diff --git a/README.md b/README.md index f84ec42..1c7180e 100644 --- a/README.md +++ b/README.md @@ -10,37 +10,24 @@ 👉 Java学习资源汇总(个人总结) -- Java基础到Java实战全套学习视频教程,包括多个企业级实战项目:https://urlify.cn/YFzABz 密码: pi95 +- **Java基础到Java实战全套学习视频教程,包括多个企业级实战项目** -- 面试算法资料,这是总结的算法资料,学完基本可以应付80%大厂:https://urlify.cn/N7vIj2 密码: ijoi +- **面试算法资料,这是总结的算法资料,学完基本可以应付80%大厂** -- 大厂面试资料,一年时间总结,覆盖Java所有技术点:https://urlify.cn/Vzmeqy 密码: j9t2 +- **大厂面试资料,一年时间总结,覆盖Java所有技术点** -- 面试思维导图,手打总结: https://urlify.cn/vUNF7z 密码: adbo +- **面试思维导图,手打总结** -👉 Java各种电子书:如果你需要各种电子书,可以移步这个仓库 [Java电子书合集](https://github.com/hello-go-maker/cs-books) +👉 **Java各种电子书:各种技术相关的电子书** -👉 Java面试思维导图(手打) +👉 **Java面试思维导图(手打)**,我靠这些导图拿到了一线互联网公司的offer,关注公众号,回复:`思维导图`; -👉 这里再分享一些我总结的**Java面试思维导图**,我靠这些导图拿到了一线互联网公司的offer,预览在下方,先来瞧瞧。 - +**划重点**:获取上面的资源,请关注我的公众号 `程序员的技术圈子`,**微信扫描下面二维码**,回复:`Java资料`,获取思维导图,绿色通道关注福利,等你拿。 +
@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)与业务层通用实现类(ServiceImplpublic 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/java/Basis/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/docs/java/Basis/Java\345\237\272\347\241\200\347\237\245\350\257\206.md"
new file mode 100644
index 0000000..5a97f4b
--- /dev/null
+++ "b/docs/java/Basis/Java\345\237\272\347\241\200\347\237\245\350\257\206.md"
@@ -0,0 +1 @@
+https://juejin.cn/post/6844904127059738631
\ No newline at end of file
diff --git a/docs/java/IO/java IO.md b/docs/java/IO/java IO.md
new file mode 100644
index 0000000..c197d76
--- /dev/null
+++ b/docs/java/IO/java IO.md
@@ -0,0 +1 @@
+https://juejin.cn/post/6844904125700784136
\ No newline at end of file
diff --git "a/docs/java/JavaFamily/\351\235\242\350\257\225\347\237\245\350\257\206\347\202\271\346\200\273\347\273\223.md" "b/docs/java/JavaFamily/\351\235\242\350\257\225\347\237\245\350\257\206\347\202\271\346\200\273\347\273\223.md"
new file mode 100644
index 0000000..459cd09
--- /dev/null
+++ "b/docs/java/JavaFamily/\351\235\242\350\257\225\347\237\245\350\257\206\347\202\271\346\200\273\347\273\223.md"
@@ -0,0 +1 @@
+https://github.com/AobingJava/JavaFamily
\ No newline at end of file
diff --git "a/docs/java/Java\345\267\245\347\250\213\345\270\210\351\235\242\350\257\225\347\252\201\345\207\273/\347\254\254\344\270\200\345\255\243\347\254\224\350\256\260.md" "b/docs/java/Java\345\267\245\347\250\213\345\270\210\351\235\242\350\257\225\347\252\201\345\207\273/\347\254\254\344\270\200\345\255\243\347\254\224\350\256\260.md"
new file mode 100644
index 0000000..6052a3f
--- /dev/null
+++ "b/docs/java/Java\345\267\245\347\250\213\345\270\210\351\235\242\350\257\225\347\252\201\345\207\273/\347\254\254\344\270\200\345\255\243\347\254\224\350\256\260.md"
@@ -0,0 +1,2 @@
+https://www.yuque.com/books/share/327d9543-85d2-418f-9315-41c3e19d2768/0dca325c876a0e85f0ba4ea48042e61d
+https://github.com/shishan100/Java-Interview-Advanced#%E5%88%86%E5%B8%83%E5%BC%8F%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97
\ No newline at end of file
diff --git "a/docs/java/Java\345\267\245\347\250\213\345\270\210\351\235\242\350\257\225\347\252\201\345\207\273/\347\254\254\344\270\211\345\255\243\347\254\224\350\256\260.md" "b/docs/java/Java\345\267\245\347\250\213\345\270\210\351\235\242\350\257\225\347\252\201\345\207\273/\347\254\254\344\270\211\345\255\243\347\254\224\350\256\260.md"
new file mode 100644
index 0000000..42773c7
--- /dev/null
+++ "b/docs/java/Java\345\267\245\347\250\213\345\270\210\351\235\242\350\257\225\347\252\201\345\207\273/\347\254\254\344\270\211\345\255\243\347\254\224\350\256\260.md"
@@ -0,0 +1 @@
+https://blog.csdn.net/u013073869/article/details/105271345
\ No newline at end of file
diff --git "a/docs/java/Java\345\267\245\347\250\213\345\270\210\351\235\242\350\257\225\347\252\201\345\207\273/\347\254\254\344\272\214\345\255\243\347\254\224\350\256\260.md" "b/docs/java/Java\345\267\245\347\250\213\345\270\210\351\235\242\350\257\225\347\252\201\345\207\273/\347\254\254\344\272\214\345\255\243\347\254\224\350\256\260.md"
new file mode 100644
index 0000000..e69de29
diff --git "a/docs/java/Multithread/Java\345\271\266\345\217\221.md" "b/docs/java/Multithread/Java\345\271\266\345\217\221.md"
new file mode 100644
index 0000000..4a068da
--- /dev/null
+++ "b/docs/java/Multithread/Java\345\271\266\345\217\221.md"
@@ -0,0 +1,2 @@
+参考:https://juejin.cn/post/6844904063687983111
+参考:https://www.cmsblogs.com/category/1391296887813967872
\ No newline at end of file
diff --git "a/docs/java/collection/Java\351\233\206\345\220\210\351\235\242\350\257\225\351\242\230.md" "b/docs/java/collection/Java\351\233\206\345\220\210\351\235\242\350\257\225\351\242\230.md"
index e69de29..85a698b 100644
--- "a/docs/java/collection/Java\351\233\206\345\220\210\351\235\242\350\257\225\351\242\230.md"
+++ "b/docs/java/collection/Java\351\233\206\345\220\210\351\235\242\350\257\225\351\242\230.md"
@@ -0,0 +1,114 @@
+### 集合面试题
+
+> ArrayList、LinkedList和Vector的区别和实现原理
+
+#### 数据结构实现
+
+ArrayList和Vector都是基于可改变大小的数据实现的,而LinkedList是基于双链表实现的。
+
+#### 增删改查效率对比
+
+ArrayList和Vector都是基于可改变大小的数据实现的,因此,从指定的位置检索对象时,或在集合的末尾插入对象、删除一个对象的时间都是O(1),但是如果在其他位置增加或者删除对象,花费的时间是O(n);
+
+而LinkedList是基于双链表实现的,因此,在插入、删除集合中的任何位置上的对象,所花费的时间都是O(1),但基于链表的数据结构在查找元素时的效率是更低的,花费的时间为O(n)。
+
+因此,从以上分析我们可以知道,查找特定的对象或者在集合末端增加或者删除对象,ArrayList和Vector的效率是ok的,如果在指定的位置删除或者插入,LinkedList的效率则更高。
+
+#### 线程安全
+
+ArrayList、LinkedList不具有线程安全性,在多线程的问题下是不能使用的,如果想要在多线程的环境下使用怎么办呢?我们可以采用Collections的静态方法synchronizedList包装一下,就可以保证线程安全了,但是在实际情况下,并不会使用这种方式,而是会采用更高级的集合进行线程安全的操作。
+
+Vector是线程安全的,其保证线程安全的机制是采用synchronized关键字,我们都知道,这个关键字的效率是不高的,在后续的很多版本中,线程安全的机制都不会采用这种方式,因此,Vector的效率是比ArrayList、LinkedList更低效的。
+
+#### 扩容机制
+
+ArrayList和Vector都是基于数据这种数据结构实现的,因此,在集合的容量满了时,是需要进行扩容操作的。
+
+在扩容时,ArrayList扩容后的容量是原先的1.5倍,扩容后,再将原先的数组中的数据拷贝到新建的数组中。
+
+Vector默认情况下,扩容后的容量是原先的2倍,除此之外,Vector还有一种可以设置**容量增量**的机制,在Vector中有capacityIncrement变量用于控制扩容时的增量,具体的规则是:当capacityIncrement大于0时,扩容时增加的大小就是capacityIncrement的大小,如果capacityIncrement小于等于0时,则将容量增加为之前的2倍。
+
+> HashMap原理分析
+
+在分析HashMap的原理之前,先说明一下,大家应该都知道HashMap在JDK1.7和1.8的实现上是有较大的区别的,而面试官也是非常喜欢考察这一个点,因此,这里也是采用这两个JDK版本对比来进行分析,这样也可以印象更加深刻一些。
+
+#### 数据结构
+
+在数据结构的实现上,大家应该都知道,JDK1.7是数组+单链表的形式,而1.8采用的是数组+单链表+红黑树,具体的表现如下:
+
+|版本|数据结构|数组+链表的实现形式|红黑树实现形式|
+|-|-|-|-|
+|JDK1.8|数组+单链表+红黑树|Node|TreeNode|
+|JDK1.7|数组+单链表|Entry|-|
+
+为了更好的让大家理解后续的讲解,这里先讲解一下HashMap中实现的一些重要参数。
+
+- 容量(capacity): HashMap中数组的长度
+ - 容量范围:必须是 2 的幂
+ - 初始容量 = 哈希表创建时的容量
+ - 默认容量 = 16 = 1<<4 = 00001中的1向左移4位 = 10000 = 十进制的 2^4 = 16
+ `static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;`
+ - 最大容量 = 2的30次方
+ `static final int MAXIMUM_CAPACITY = 1 << 30;`
+
+- 加载因子(Load factor):HashMap在其容量自动增加时,会设置加载因子,当达到设置的值时,就会触发自动扩容。
+ - 加载因子越大、填满的元素越多,也就是说,空间利用率高、但冲突的机会加大、查找效率变低
+ - 加载因子越小、填满的元素越少,也就是说,空间利用率小、冲突的机会减小、查找效率高
+ // 实际加载因子
+ `final float loadFactor;`
+ // 默认加载因子 = 0.75
+ `static final float DEFAULT_LOAD_FACTOR = 0.75f;`
+
+- 扩容阈值(threshold):当哈希表的大小 ≥ 扩容阈值时,就会扩容哈希表(即扩充HashMap的容量)。
+ - 扩容 = 对哈希表进行resize操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数
+ - 扩容阈值 = 容量 x 加载因子
+
+#### 获取数据(get)
+
+HashMap的获取数据的过程大致如下:
+
+- 首先,根据key判断是否为空值;
+- 如果为空,则到hashmap数组的第1个位置,寻找对应key为null的键;
+- 如果不为空,则根据key计算hash值;
+- 根据得到的hash值采用`hash & (length - 1)`的计算方式得到key在数组中的位置;
+- 结束。
+
+以上就是大致的数据获取流程,接下来,我们再对JDK1.7和1.8获取数据的细节做一个对比。
+
+|版本|hash值的计算方式|
+|-|-|
+|JDK1.8|1、hash = (key == null) ? 0 : hash(key); private static final Protocol protocol =
+ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtensi
+on();
+复制代码
+
+* 工厂模式
+
+ Provider 在 export 服务时,会调用 ServiceConfig 的 export 方法。ServiceConfig中有个字段:
+
+private static final Protocol protocol =
+ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtensi
+on();
+复制代码
+
+Dubbo 里有很多这种代码。这也是一种工厂模式,只是实现类的获取采用了 JDKSPI 的机制。这么实现的优点是可扩展性强,想要扩展实现,只需要在 classpath下增加个文件就可以了,代码零侵入。另外,像上面的 Adaptive 实现,可以做到调用时动态决定调用哪个实现,但是由于这种实现采用了动态代理,会造成代码调试比较麻烦,需要分析出实际调用的实现类。
+
+* **装饰器模式**
+
+ Dubbo 在启动和调用阶段都大量使用了装饰器模式。以 Provider 提供的调用链为例,具体的调用链代码是在 ProtocolFilterWrapper 的 buildInvokerChain 完成的,具体是将注解中含有 group=provider 的 Filter 实现,按照 order 排序,最后的调用顺序是:
+
+EchoFilter -> ClassLoaderFilter -> GenericFilter -> ContextFilter ->
+ExecuteLimitFilter -> TraceFilter -> TimeoutFilter -> MonitorFilter ->
+ExceptionFilter
+复制代码
+
+更确切地说,这里是装饰器和责任链模式的混合使用。例如,EchoFilter 的作用是判断是否是回声测试请求,是的话直接返回内容,这是一种责任链的体现。而像ClassLoaderFilter 则只是在主功能上添加了功能,更改当前线程的 ClassLoader,这是典型的装饰器模式。
+
+* **观察者模式**
+
+ Dubbo 的 Provider 启动时,需要与注册中心交互,先注册自己的服务,再订阅自己的服务,订阅时,采用了观察者模式,开启一个 listener。注册中心会每 5 秒定时检查是否有服务更新,如果有更新,向该服务的提供者发送一个 notify 消息,provider 接受到 notify 消息后,运行 NotifyListener 的 notify 方法,执行监听器方法。
+
+* **动态代理模式**
+
+ Dubbo 扩展 JDK SPI 的类 ExtensionLoader 的 Adaptive 实现是典型的动态代理实现。Dubbo 需要灵活地控制实现类,即在调用阶段动态地根据参数决定调用哪个实现类,所以采用先生成代理类的方法,能够做到灵活的调用。生成代理类的代码是 ExtensionLoader 的 createAdaptiveExtensionClassCode 方法。代理类主要逻辑是,获取 URL 参数中指定参数的值作为获取实现类的 key。
+
+## 运维管理
+
+### 服务上线怎么兼容旧版本?
+
+* 可以用版本号(version)过渡,多个不同版本的服务注册到注册中心,版本号不同的服务相互间不引用。这个和服务分组的概念有一点类似。
+
+### Dubbo telnet 命令能做什么?
+
+* dubbo 服务发布之后,我们可以利用 telnet 命令进行调试、管理。Dubbo2.0.5 以上版本服务提供端口支持 telnet 命令
+
+### Dubbo 支持服务降级吗?
+
+* 以通过 dubbo:reference 中设置 mock=“return null”。mock 的值也可以修改为 true,然后再跟接口同一个路径下实现一个 Mock 类,命名规则是 “接口名称+Mock” 后缀。然后在 Mock 类里实现自己的降级逻辑
+
+### Dubbo 如何优雅停机?
+
+* Dubbo 是通过 JDK 的 ShutdownHook 来完成优雅停机的,所以如果使用kill -9 PID 等强制关闭指令,是不会执行优雅停机的,只有通过 kill PID 时,才会执行。
+
+## SPI
+
+### Dubbo SPI 和 Java SPI 区别?
+
+* JDK SPI:
+
+ JDK 标准的 SPI 会一次性加载所有的扩展实现,如果有的扩展很耗时,但也没用上,很浪费资源。所以只希望加载某个的实现,就不现实了
+
+* DUBBO SPI:
+
+ 1、对 Dubbo 进行扩展,不需要改动 Dubbo 的源码
+
+ 2、延迟加载,可以一次只加载自己想要加载的扩展实现。
+
+ 3、增加了对扩展点 IOC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。
+
+ 4、Dubbo 的扩展机制能很好的支持第三方 IoC 容器,默认支持 Spring Bean。
+
+## 其他
+
+### Dubbo 支持分布式事务吗?
+
+* 目前暂时不支持,可与通过 tcc-transaction 框架实现
+
+* 介绍:tcc-transaction 是开源的 TCC 补偿性分布式事务框架
+
+* TCC-Transaction 通过 Dubbo 隐式传参的功能,避免自己对业务代码的入侵。
+
+### Dubbo 可以对结果进行缓存吗?
+
+* 为了提高数据访问的速度。Dubbo 提供了声明式缓存,以减少用户加缓存的工作量
+ io.netty
+ netty-all
+ 4.1.16.Final
+
+```
+
+* 2、NettyServer 模板,看起来代码那么多,`其实只需要添加一行消息就好了`
+* `请认真看中间的代码`
+
+```java
+package com.lijie.iob;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.*;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.codec.serialization.ClassResolvers;
+import io.netty.handler.codec.serialization.ObjectEncoder;
+import io.netty.handler.codec.string.StringDecoder;
+
+public class NettyServer {
+ public static void main(String[] args) throws InterruptedException {
+ EventLoopGroup bossGroup = new NioEventLoopGroup();
+ EventLoopGroup workerGroup = new NioEventLoopGroup();
+ try {
+ ServerBootstrap b = new ServerBootstrap();
+ b.group(bossGroup, workerGroup)
+ .channel(NioServerSocketChannel.class)
+ .childHandler(new ChannelInitializer() {
+ @Override
+ protected void initChannel(SocketChannel socketChannel) throws Exception {
+ ChannelPipeline pipeline = socketChannel.pipeline();
+ pipeline.addLast(new StringDecoder());
+ pipeline.addLast("encoder", new ObjectEncoder());
+ pipeline.addLast(" decoder", new io.netty.handler.codec.serialization.ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
+
+ //重点,其他的都是复用的
+ //这是真正的I0的业务代码,把他封装成一个个的个Hand1e类就行了
+ //把他当成 SpringMVC的Controller
+ pipeline.addLast(new NettyServerHandler());
+
+ }
+ })
+ .option(ChannelOption.SO_BACKLOG, 128)
+ .childOption(ChannelOption.SO_KEEPALIVE, true);
+ ChannelFuture f = b.bind(8000).sync();
+ System.out.println("服务端启动成功,端口号为:" + 8000);
+ f.channel().closeFuture().sync();
+ } finally {
+ workerGroup.shutdownGracefully();
+ bossGroup.shutdownGracefully();
+ }
+ }
+}
+```
+
+* 3、需要做的IO操作,重点是继承ChannelInboundHandlerAdapter类就好了
+
+```java
+package com.lijie.iob;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+
+public class NettyServerHandler extends ChannelInboundHandlerAdapter {
+ RequestHandler requestHandler = new RequestHandler();
+
+ @Override
+ public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
+ Channel channel = ctx.channel();
+ System.out.println(String.format("客户端信息: %s", channel.remoteAddress()));
+ }
+
+ @Override
+ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+ Channel channel = ctx.channel();
+ String request = (String) msg;
+ System.out.println(String.format("客户端发送的消息 %s : %s", channel.remoteAddress(), request));
+ String response = requestHandler.handle(request);
+ ctx.write(response);
+ ctx.flush();
+ }
+}
+```
+
+* 4 客户的代码还是之前NIO的代码,我在复制下来一下吧
+
+```java
+package com.test.io;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.util.Scanner;
+
+//TCP协议Socket:客户端
+public class Client01 {
+ public static void main(String[] args) throws IOException {
+ //创建套接字对象socket并封装ip与port
+ Socket socket = new Socket("127.0.0.1", 8000);
+ //根据创建的socket对象获得一个输出流
+ OutputStream outputStream = socket.getOutputStream();
+ //控制台输入以IO的形式发送到服务器
+ System.out.println("TCP连接成功 \n请输入:");
+ while(true){
+ byte[] car = new Scanner(System.in).nextLine().getBytes();
+ outputStream.write(car);
+ System.out.println("TCP协议的Socket发送成功");
+ //刷新缓冲区
+ outputStream.flush();
+ }
+ }
+}
+```
+
+* 运行测试,还是之前那样,启动服务端,在启动所有客户端控制台输入就好了:
+
+作者:小杰要吃蛋
+链接:https://juejin.cn/post/6844904125700784136
+来源:稀土掘金
+著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
\ No newline at end of file
diff --git "a/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\345\237\272\347\241\200.md" "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\345\237\272\347\241\200.md"
new file mode 100644
index 0000000..e4df817
--- /dev/null
+++ "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\345\237\272\347\241\200.md"
@@ -0,0 +1,1313 @@
+
+
+## Java概述
+
+### 何为编程
+
+* 编程就是让计算机为解决某个问题而使用某种程序设计语言编写程序代码,并最终得到结果的过程。
+
+* 为了使计算机能够理解人的意图,人类就必须要将需解决的问题的思路、方法、和手段通过计算机能够理解的形式告诉计算机,使得计算机能够根据人的指令一步一步去工作,完成某种特定的任务。这种人和计算机之间交流的过程就是编程。
+
+### 什么是Java
+
+* Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程 。
+
+### jdk1.5之后的三大版本
+
+* Java SE(J2SE,Java 2 Platform Standard Edition,标准版)
+ Java SE 以前称为 J2SE。它允许开发和部署在桌面、服务器、嵌入式环境和实时环境中使用的 Java 应用程序。Java SE 包含了支持 Java Web 服务开发的类,并为Java EE和Java ME提供基础。
+* Java EE(J2EE,Java 2 Platform Enterprise Edition,企业版)
+ Java EE 以前称为 J2EE。企业版本帮助开发和部署可移植、健壮、可伸缩且安全的服务器端Java 应用程序。Java EE 是在 Java SE 的基础上构建的,它提供 Web 服务、组件模型、管理和通信 API,可以用来实现企业级的面向服务体系结构(service-oriented architecture,SOA)和 Web2.0应用程序。2018年2月,Eclipse 宣布正式将 JavaEE 更名为 JakartaEE
+* Java ME(J2ME,Java 2 Platform Micro Edition,微型版)
+ Java ME 以前称为 J2ME。Java ME 为在移动设备和嵌入式设备(比如手机、PDA、电视机顶盒和打印机)上运行的应用程序提供一个健壮且灵活的环境。Java ME 包括灵活的用户界面、健壮的安全模型、许多内置的网络协议以及对可以动态下载的连网和离线应用程序的丰富支持。基于 Java ME 规范的应用程序只需编写一次,就可以用于许多设备,而且可以利用每个设备的本机功能。
+
+### 3 Jdk和Jre和JVM的区别
+
+`看Java官方的图片,Jdk中包括了Jre,Jre中包括了JVM`
+
+* JDK :Jdk还包括了一些Jre之外的东西 ,就是这些东西帮我们编译Java代码的, 还有就是监控Jvm的一些工具 Java Development Kit是提供给Java开发人员使用的,其中包含了Java的开发工具,也包括了JRE。所以安装了JDK,就无需再单独安装JRE了。其中的开发工具:编译工具(javac.exe),打包工具(jar.exe)等
+
+* JRE :Jre大部分都是 C 和 C++ 语言编写的,他是我们在编译java时所需要的基础的类库 Java Runtime Environment包括Java虚拟机和Java程序所需的核心类库等。核心类库主要是java.lang包:包含了运行Java程序必不可少的系统类,如基本数据类型、基本数学函数、字符串处理、线程、异常处理类等,系统缺省加载这个包
+
+ 如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。
+
+* Jvm:在倒数第二层 由他可以在(最后一层的)各种平台上运行 Java Virtual Machine是Java虚拟机,Java程序需要运行在虚拟机上,不同的平台有自己的虚拟机,因此Java语言可以实现跨平台。
+
+
+### 什么是跨平台性?原理是什么
+
+* 所谓跨平台性,是指java语言编写的程序,一次编译后,可以在多个系统平台上运行。
+
+* 实现原理:Java程序是通过java虚拟机在系统平台上运行的,只要该系统可以安装相应的java虚拟机,该系统就可以运行java程序。
+
+### Java语言有哪些特点
+
+* 简单易学(Java语言的语法与C语言和C++语言很接近)
+
+* 面向对象(封装,继承,多态)
+
+* 平台无关性(Java虚拟机实现平台无关性)
+
+* 支持网络编程并且很方便(Java语言诞生本身就是为简化网络编程设计的)
+
+* 支持多线程(多线程机制使应用程序在同一时间并行执行多项任)
+
+* 健壮性(Java语言的强类型机制、异常处理、垃圾的自动收集等)
+
+* 安全性好
+
+### 什么是字节码?采用字节码的最大好处是什么
+
+* **字节码**:Java源代码经过虚拟机编译器编译后产生的文件(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。
+
+* **采用字节码的好处**:
+
+ Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。
+
+* **先看下java中的编译器和解释器**:
+
+ Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做字节码(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行,这就是上面提到的Java的特点的编译与解释并存的解释。
+
+ Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机器可执行的二进制机器码---->程序运行。
+
+### 什么是Java程序的主类?应用程序和小程序的主类有何不同?
+
+* 一个程序中可以有多个类,但只能有一个类是主类。在Java应用程序中,这个主类是指包含main()方法的类。而在Java小程序中,这个主类是一个继承自系统类JApplet或Applet的子类。应用程序的主类不一定要求是public类,但小程序的主类要求必须是public类。主类是Java程序执行的入口点。
+
+### Java应用程序与小程序之间有那些差别?
+
+* 简单说应用程序是从主线程启动(也就是main()方法)。applet小程序没有main方法,主要是嵌在浏览器页面上运行(调用init()线程或者run()来启动),嵌入浏览器这点跟flash的小游戏类似。
+
+### Java和C++的区别
+
+`我知道很多人没学过C++,但是面试官就是没事喜欢拿咱们Java和C++比呀!没办法!!!就算没学过C++,也要记下来!`
+
+* 都是面向对象的语言,都支持封装、继承和多态
+* Java不提供指针来直接访问内存,程序内存更加安全
+* Java的类是单继承的,C++支持多重继承;虽然Java的类不可以多继承,但是接口可以多继承。
+* Java有自动内存管理机制,不需要程序员手动释放无用内存
+
+### Oracle JDK 和 OpenJDK 的对比
+
+1. Oracle JDK版本将每三年发布一次,而OpenJDK版本每三个月发布一次;
+
+2. OpenJDK 是一个参考模型并且是完全开源的,而Oracle JDK是OpenJDK的一个实现,并不是完全开源的;
+
+3. Oracle JDK 比 OpenJDK 更稳定。OpenJDK和Oracle JDK的代码几乎相同,但Oracle JDK有更多的类和一些错误修复。因此,如果您想开发企业/商业软件,我建议您选择Oracle JDK,因为它经过了彻底的测试和稳定。某些情况下,有些人提到在使用OpenJDK 可能会遇到了许多应用程序崩溃的问题,但是,只需切换到Oracle JDK就可以解决问题;
+
+4. 在响应性和JVM性能方面,Oracle JDK与OpenJDK相比提供了更好的性能;
+
+5. Oracle JDK不会为即将发布的版本提供长期支持,用户每次都必须通过更新到最新版本获得支持来获取最新版本;
+
+6. Oracle JDK根据二进制代码许可协议获得许可,而OpenJDK根据GPL v2许可获得许可。
+
+## 基础语法
+
+### 数据类型
+
+#### Java有哪些数据类型
+
+**定义**:Java语言是强类型语言,对于每一种数据都定义了明确的具体的数据类型,在内存中分配了不同大小的内存空间。
+
+**分类**
+
+* 基本数据类型
+ * 数值型
+ * 整数类型(byte,short,int,long)
+ * 浮点类型(float,double)
+ * 字符型(char)
+ * 布尔型(boolean)
+* 引用数据类型
+ * 类(class)
+ * 接口(interface)
+ * 数组([])
+
+**Java基本数据类型图**
+
+
+#### switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String 上
+
+* 在 Java 5 以前,switch(expr)中,expr 只能是 byte、short、char、int。从 Java5 开始,Java 中引入了枚举类型,expr 也可以是 enum 类型,从 Java 7 开始,expr 还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。
+
+#### 用最有效率的方法计算 2 乘以 8
+
+* 2 << 3(左移 3 位相当于乘以 2 的 3 次方,右移 3 位相当于除以 2 的 3 次方)。
+
+#### Math.round(11.5) 等于多少?Math.round(-11.5)等于多少
+
+* Math.round(11.5)的返回值是 12,Math.round(-11.5)的返回值是-11。四舍五入的原理是在参数上加 0.5 然后进行下取整。
+
+#### float f=3.4;是否正确
+
+* 不正确。3.4 是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换float f =(float)3.4; 或者写成 float f =3.4F;。
+
+#### short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗
+
+* 对于 short s1 = 1; s1 = s1 + 1;由于 1 是 int 类型,因此 s1+1 运算结果也是 int型,需要强制转换类型才能赋值给 short 型。
+
+* 而 short s1 = 1; s1 += 1;可以正确编译,因为 s1+= 1;相当于 s1 = (short(s1 + 1);其中有隐含的强制类型转换。
+
+### 编码
+
+#### Java语言采用何种编码方案?有何特点?
+
+* Java语言采用Unicode编码标准,Unicode(标准码),它为每个字符制订了一个唯一的数值,因此在任何的语言,平台,程序都可以放心的使用。
+
+### 注释
+
+#### 什么Java注释
+
+**定义**:用于解释说明程序的文字
+
+**分类**
+
+* 单行注释
+ 格式: // 注释文字
+* 多行注释
+ 格式: /* 注释文字 */
+* 文档注释
+ 格式:/** 注释文字 */
+
+**作用**
+
+* 在程序中,尤其是复杂的程序中,适当地加入注释可以增加程序的可读性,有利于程序的修改、调试和交流。注释的内容在程序编译的时候会被忽视,不会产生目标代码,注释的部分不会对程序的执行结果产生任何影响。
+
+`注意事项:多行和文档注释都不能嵌套使用。`
+
+### 访问修饰符
+
+#### 访问修饰符 public,private,protected,以及不写(默认)时的区别
+
+* **定义**:Java中,可以使用访问修饰符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。
+
+* **分类**
+
+ * private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
+
+ * default (即缺省,什么也不写,不使用任何关键字): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
+
+ * protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
+
+ * public : 对所有类可见。使用对象:类、接口、变量、方法
+
+**访问修饰符图**
+
+
+### 运算符
+
+#### &和&&的区别
+
+* &运算符有两种用法:(1)按位与;(2)逻辑与。
+
+* &&运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是true 整个表达式的值才是 true。&&之所以称为短路运算,是因为如果&&左边的表达式的值是 false,右边的表达式会被直接短路掉,不会进行运算。
+
+`注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。`
+
+### 关键字
+
+#### Java 有没有 goto
+
+* goto 是 Java 中的保留字,在目前版本的 Java 中没有使用。
+
+#### final 有什么用?
+
+`用于修饰类、属性和方法;`
+
+* 被final修饰的类不可以被继承
+* 被final修饰的方法不可以被重写
+* 被final修饰的变量不可以被改变,被final修饰不可变的是变量的引用,而不是引用指向的内容,引用指向的内容是可以改变的
+
+#### final finally finalize区别
+
+* final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表 示该变量是一个常量不能被重新赋值。
+* finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块 中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
+* finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调 用,当我们调用System.gc() 方法的时候,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的 最后判断。
+
+#### this关键字的用法
+
+* this是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针。
+
+* this的用法在java中大体可以分为3种:
+
+ * 1.普通的直接引用,this相当于是指向当前对象本身。
+
+ * 2.形参与成员名字重名,用this来区分:
+
+ public Person(String name, int age) {
+ this.name = name;
+ this.age = age;
+ }
+ 复制代码
+ * 3.引用本类的构造函数
+
+ class Person{
+ private String name;
+ private int age;
+
+ public Person() {
+ }
+
+ public Person(String name) {
+ this.name = name;
+ }
+ public Person(String name, int age) {
+ this(name);
+ this.age = age;
+ }
+ }
+ 复制代码
+
+#### super关键字的用法
+
+* super可以理解为是指向自己超(父)类对象的一个指针,而这个超类指的是离自己最近的一个父类。
+
+* super也有三种用法:
+
+ * 1.普通的直接引用
+
+ 与this类似,super相当于是指向当前对象的父类的引用,这样就可以用super.xxx来引用父类的成员。
+
+ * 2.子类中的成员变量或方法与父类中的成员变量或方法同名时,用super进行区分
+
+ class Person{
+ protected String name;
+
+ public Person(String name) {
+ this.name = name;
+ }
+
+ }
+
+ class Student extends Person{
+ private String name;
+
+ public Student(String name, String name1) {
+ super(name);
+ this.name = name1;
+ }
+
+ public void getInfo(){
+ System.out.println(this.name); //Child
+ System.out.println(super.name); //Father
+ }
+
+ }
+
+ public class Test {
+ public static void main(String[] args) {
+ Student s1 = new Student("Father","Child");
+ s1.getInfo();
+
+ }
+ }
+ 复制代码
+ * 3.引用父类构造函数
+
+ * super(参数):调用父类中的某一个构造函数(应该为构造函数中的第一条语句)。
+ * this(参数):调用本类中另一种形式的构造函数(应该为构造函数中的第一条语句)。
+
+#### this与super的区别
+
+* super: 它引用当前对象的直接父类中的成员(用来访问直接父类中被隐藏的父类中成员数据或函数,基类与派生类中有相同成员定义时如:super.变量名 super.成员函数据名(实参)
+* this:它代表当前对象名(在程序中易产生二义性之处,应使用this来指明当前对象;如果函数的形参与类中的成员数据同名,这时需用this来指明成员变量名)
+* super()和this()类似,区别是,super()在子类中调用父类的构造方法,this()在本类内调用本类的其它构造方法。
+* super()和this()均需放在构造方法内第一行。
+* 尽管可以用this调用一个构造器,但却不能调用两个。
+* this和super不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
+* this()和super()都指的是对象,所以,均不可以在static环境中使用。包括:static变量,static方法,static语句块。
+* 从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字。
+
+#### static存在的主要意义
+
+* static的主要意义是在于创建独立于具体对象的域变量或者方法。**以致于即使没有创建对象,也能使用属性和调用方法**!
+
+* static关键字还有一个比较关键的作用就是 **用来形成静态代码块以优化程序性能**。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。
+
+* 为什么说static块可以用来优化程序性能,是因为它的特性:只会在类加载的时候执行一次。因此,很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。
+
+#### static的独特之处
+
+* 1、被static修饰的变量或者方法是独立于该类的任何对象,也就是说,这些变量和方法**不属于任何一个实例对象,而是被类的实例对象所共享**。
+
+> 怎么理解 “被类的实例对象所共享” 这句话呢?就是说,一个类的静态成员,它是属于大伙的【大伙指的是这个类的多个对象实例,我们都知道一个类可以创建多个实例!】,所有的类对象共享的,不像成员变量是自个的【自个指的是这个类的单个实例对象】…我觉得我已经讲的很通俗了,你明白了咩?
+
+* 2、在该类被第一次加载的时候,就会去加载被static修饰的部分,而且只在类第一次使用时加载并进行初始化,注意这是第一次用就要初始化,后面根据需要是可以再次赋值的。
+
+* 3、static变量值在类加载的时候分配空间,以后创建类对象的时候不会重新分配。赋值的话,是可以任意赋值的!
+
+* 4、被static修饰的变量或者方法是优先于对象存在的,也就是说当一个类加载完毕之后,即便没有创建对象,也可以去访问。
+
+#### static应用场景
+
+* 因为static是被类的实例对象所共享,因此如果**某个成员变量是被所有对象所共享的,那么这个成员变量就应该定义为静态变量**。
+
+* 因此比较常见的static应用场景有:
+
+> 1、修饰成员变量 2、修饰成员方法 3、静态代码块 4、修饰类【只能修饰内部类也就是静态内部类】 5、静态导包
+
+#### static注意事项
+
+* 1、静态只能访问静态。
+* 2、非静态既可以访问非静态的,也可以访问静态的。
+
+### 流程控制语句
+
+#### break ,continue ,return 的区别及作用
+
+* break 跳出总上一层循环,不再执行循环(结束当前的循环体)
+
+* continue 跳出本次循环,继续执行下次循环(结束正在执行的循环 进入下一个循环条件)
+
+* return 程序返回,不再执行下面的代码(结束当前的方法 直接返回)
+
+#### 在 Java 中,如何跳出当前的多重嵌套循环
+
+* 在Java中,要想跳出多重循环,可以在外面的循环语句前定义一个标号,然后在里层循环体的代码中使用带有标号的break 语句,即可跳出外层循环。例如:
+
+ public static void main(String[] args) {
+ ok:
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ System.out.println("i=" + i + ",j=" + j);
+ if (j == 5) {
+ break ok;
+ }
+ }
+ }
+ }
+ 复制代码
+
+## 面向对象
+
+### 面向对象概述
+
+#### 面向对象和面向过程的区别
+
+* **面向过程**:
+
+ * 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
+
+ * 缺点:没有面向对象易维护、易复用、易扩展
+
+* **面向对象**:
+
+ * 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
+
+ * 缺点:性能比面向过程低
+
+`面向过程是具体化的,流程化的,解决一个问题,你需要一步一步的分析,一步一步的实现。`
+
+`面向对象是模型化的,你只需抽象出一个类,这是一个封闭的盒子,在这里你拥有数据也拥有解决问题的方法。需要什么功能直接使用就可以了,不必去一步一步的实现,至于这个功能是如何实现的,管我们什么事?我们会用就可以了。`
+
+`面向对象的底层其实还是面向过程,把面向过程抽象成类,然后封装,方便我们使用的就是面向对象了。`
+
+### 面向对象三大特性
+
+#### 面向对象的特征有哪些方面
+
+**面向对象的特征主要有以下几个方面**:
+
+* **抽象**:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
+
+* **封装**把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。
+
+* **继承**是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。
+
+ * 关于继承如下 3 点请记住:
+
+ * 子类拥有父类非 private 的属性和方法。
+
+ * 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
+
+ * 子类可以用自己的方式实现父类的方法。(以后介绍)。
+
+* **多态**:父类或接口定义的引用变量可以指向子类或具体实现类的实例对象。提高了程序的拓展性。在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
+
+#### 什么是多态机制?Java语言是如何实现多态的?
+
+* 所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
+
+* 多态分为编译时多态和运行时多态。其中编辑时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。
+
+**多态的实现**
+
+* Java实现多态有三个必要条件:继承、重写、向上转型。
+
+ * 继承:在多态中必须存在有继承关系的子类和父类。
+
+ * 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
+
+ * 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。
+
+`只有满足了上述三个条件,我们才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。`
+
+`对于Java而言,它多态的实现机制遵循一个原则:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。`
+
+#### 面向对象五大基本原则是什么(可选)
+
+* 单一职责原则SRP(Single Responsibility Principle)
+ 类的功能要单一,不能包罗万象,跟杂货铺似的。
+* 开放封闭原则OCP(Open-Close Principle)
+ 一个模块对于拓展是开放的,对于修改是封闭的,想要增加功能热烈欢迎,想要修改,哼,一万个不乐意。
+* 里式替换原则LSP(the Liskov Substitution Principle LSP)
+ 子类可以替换父类出现在父类能够出现的任何地方。比如你能代表你爸去你姥姥家干活。哈哈~~
+* 依赖倒置原则DIP(the Dependency Inversion Principle DIP)
+ 高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。就是你出国要说你是中国人,而不能说你是哪个村子的。比如说中国人是抽象的,下面有具体的xx省,xx市,xx县。你要依赖的抽象是中国人,而不是你是xx村的。
+* 接口分离原则ISP(the Interface Segregation Principle ISP)
+ 设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好。就比如一个手机拥有打电话,看视频,玩游戏等功能,把这几个功能拆分成不同的接口,比在一个接口里要好的多。
+
+### 类与接口
+
+#### 抽象类和接口的对比
+
+* 抽象类是用来捕捉子类的通用特性的。接口是抽象方法的集合。
+
+* 从设计层面来说,抽象类是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
+
+**相同点**
+
+* 接口和抽象类都不能实例化
+* 都位于继承的顶端,用于被其他实现或继承
+* 都包含抽象方法,其子类都必须覆写这些抽象方法
+
+**不同点**
+
+| 参数 | 抽象类 | 接口 |
+| --- | --- | --- |
+| 声明 | 抽象类使用abstract关键字声明 | 接口使用interface关键字声明 |
+| 实现 | 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现 | 子类使用implements关键字来实现接口。它需要提供接口中所有声明的方法的实现 |
+| 构造器 | 抽象类可以有构造器 | 接口不能有构造器 |
+| 访问修饰符 | 抽象类中的方法可以是任意访问修饰符 | 接口方法默认修饰符是public。并且不允许定义为 private 或者 protected |
+| 多继承 | 一个类最多只能继承一个抽象类 | 一个类可以实现多个接口 |
+| 字段声明 | 抽象类的字段声明可以是任意的 | 接口的字段默认都是 static 和 final 的 |
+
+**备注**:Java8中接口中引入默认方法和静态方法,以此来减少抽象类和接口之间的差异。
+
+`现在,我们可以为接口提供默认实现的方法了,并且不用强制子类来实现它。`
+
+* 接口和抽象类各有优缺点,在接口和抽象类的选择上,必须遵守这样一个原则:
+ * 行为模型应该总是通过接口而不是抽象类定义,所以通常是优先选用接口,尽量少用抽象类。
+ * 选择抽象类的时候通常是如下情况:需要定义子类的行为,又要为子类提供通用的功能。
+
+#### 普通类和抽象类有哪些区别?
+
+* 普通类不能包含抽象方法,抽象类可以包含抽象方法。
+* 抽象类不能直接实例化,普通类可以直接实例化。
+
+#### 抽象类能使用 final 修饰吗?
+
+* 不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类
+
+#### 创建一个对象用什么关键字?对象实例与对象引用有何不同?
+
+* new关键字,new创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向0个或1个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有n个引用指向它(可以用n条绳子系住一个气球)
+
+### 变量与方法
+
+#### 成员变量与局部变量的区别有哪些
+
+* 变量:在程序执行的过程中,在某个范围内其值可以发生改变的量。从本质上讲,变量其实是内存中的一小块区域
+
+* 成员变量:方法外部,类内部定义的变量
+
+* 局部变量:类的方法中的变量。
+
+* 成员变量和局部变量的区别
+
+**作用域**
+
+* 成员变量:针对整个类有效。
+* 局部变量:只在某个范围内有效。(一般指的就是方法,语句体内)
+
+**存储位置**
+
+* 成员变量:随着对象的创建而存在,随着对象的消失而消失,存储在堆内存中。
+* 局部变量:在方法被调用,或者语句被执行的时候存在,存储在栈内存中。当方法调用完,或者语句结束后,就自动释放。
+
+**生命周期**
+
+* 成员变量:随着对象的创建而存在,随着对象的消失而消失
+* 局部变量:当方法调用完,或者语句结束后,就自动释放。
+
+**初始值**
+
+* 成员变量:有默认初始值。
+* 局部变量:没有默认初始值,使用前必须赋值。
+
+#### 在Java中定义一个不做事且没有参数的构造方法的作用
+
+* Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用super()来调用父类中特定的构造方法,则编译时将发生错误,因为Java程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。
+
+#### 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?
+
+* 帮助子类做初始化工作。
+
+#### 一个类的构造方法的作用是什么?若一个类没有声明构造方法,改程序能正确执行吗?为什么?
+
+* 主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。
+
+#### 构造方法有哪些特性?
+
+* 名字与类名相同;
+
+* 没有返回值,但不能用void声明构造函数;
+
+* 生成类的对象时自动执行,无需调用。
+
+#### 静态变量和实例变量区别
+
+* 静态变量: 静态变量由于不属于任何实例对象,属于类的,所以在内存中只会有一份,在类的加载过程中,JVM只为静态变量分配一次内存空间。
+
+* 实例变量: 每次创建对象,都会为每个对象分配成员变量内存空间,实例变量是属于实例对象的,在内存中,创建几次对象,就有几份成员变量。
+
+#### 静态变量与普通变量区别
+
+* static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
+
+* 还有一点就是static成员变量的初始化顺序按照定义的顺序进行初始化。
+
+#### 静态方法和实例方法有何不同?
+
+`静态方法和实例方法的区别主要体现在两个方面:`
+
+* 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
+
+* 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制
+
+#### 在一个静态方法内调用一个非静态成员为什么是非法的?
+
+* 由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。
+
+#### 什么是方法的返回值?返回值的作用是什么?
+
+* 方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用:接收出结果,使得它可以用于其他的操作!
+
+### 内部类
+
+#### 什么是内部类?
+
+* 在Java中,可以将一个类的定义放在另外一个类的定义内部,这就是**内部类**。内部类本身就是类的一个属性,与其他属性定义方式一致。
+
+#### 内部类的分类有哪些
+
+`内部类可以分为四种:**成员内部类、局部内部类、匿名内部类和静态内部类**。`
+
+##### 静态内部类
+
+* 定义在类内部的静态类,就是静态内部类。
+
+ public class Outer {
+
+ private static int radius = 1;
+
+ static class StaticInner {
+ public void visit() {
+ System.out.println("visit outer static variable:" + radius);
+ }
+ }
+ }
+ 复制代码
+* 静态内部类可以访问外部类所有的静态变量,而不可访问外部类的非静态变量;静态内部类的创建方式,`new 外部类.静态内部类()`,如下:
+
+ Outer.StaticInner inner = new Outer.StaticInner();
+ inner.visit();
+ 复制代码
+
+##### 成员内部类
+
+* 定义在类内部,成员位置上的非静态类,就是成员内部类。
+
+ public class Outer {
+
+ private static int radius = 1;
+ private int count =2;
+
+ class Inner {
+ public void visit() {
+ System.out.println("visit outer static variable:" + radius);
+ System.out.println("visit outer variable:" + count);
+ }
+ }
+ }
+ 复制代码
+* 成员内部类可以访问外部类所有的变量和方法,包括静态和非静态,私有和公有。成员内部类依赖于外部类的实例,它的创建方式`外部类实例.new 内部类()`,如下:
+
+ Outer outer = new Outer();
+ Outer.Inner inner = outer.new Inner();
+ inner.visit();
+ 复制代码
+
+##### 局部内部类
+
+* 定义在方法中的内部类,就是局部内部类。
+
+ public class Outer {
+
+ private int out_a = 1;
+ private static int STATIC_b = 2;
+
+ public void testFunctionClass(){
+ int inner_c =3;
+ class Inner {
+ private void fun(){
+ System.out.println(out_a);
+ System.out.println(STATIC_b);
+ System.out.println(inner_c);
+ }
+ }
+ Inner inner = new Inner();
+ inner.fun();
+ }
+ public static void testStaticFunctionClass(){
+ int d =3;
+ class Inner {
+ private void fun(){
+ // System.out.println(out_a); 编译错误,定义在静态方法中的局部类不可以访问外部类的实例变量
+ System.out.println(STATIC_b);
+ System.out.println(d);
+ }
+ }
+ Inner inner = new Inner();
+ inner.fun();
+ }
+ }
+ 复制代码
+* 定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和方法。局部内部类的创建方式,在对应方法内,`new 内部类()`,如下:
+
+ public static void testStaticFunctionClass(){
+ class Inner {
+ }
+ Inner inner = new Inner();
+ }
+ 复制代码
+
+##### 匿名内部类
+
+* 匿名内部类就是没有名字的内部类,日常开发中使用的比较多。
+
+ public class Outer {
+
+ private void test(final int i) {
+ new Service() {
+ public void method() {
+ for (int j = 0; j < i; j++) {
+ System.out.println("匿名内部类" );
+ }
+ }
+ }.method();
+ }
+ }
+ //匿名内部类必须继承或实现一个已有的接口
+ interface Service{
+ void method();
+ }
+ 复制代码
+* 除了没有名字,匿名内部类还有以下特点:
+
+ * 匿名内部类必须继承一个抽象类或者实现一个接口。
+ * 匿名内部类不能定义任何静态成员和静态方法。
+ * 当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。
+ * 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
+* 匿名内部类创建方式:
+
+ new 类/接口{
+ //匿名内部类实现部分
+ }
+ 复制代码
+
+#### 内部类的优点
+
+`我们为什么要使用内部类呢?因为它有以下优点:`
+
+* 一个内部类对象可以访问创建它的外部类对象的内容,包括私有数据!
+* 内部类不为同一包的其他类所见,具有很好的封装性;
+* 内部类有效实现了“多重继承”,优化 java 单继承的缺陷。
+* 匿名内部类可以很方便的定义回调。
+
+#### 内部类有哪些应用场景
+
+1. 一些多算法场合
+2. 解决一些非面向对象的语句块。
+3. 适当使用内部类,使得代码更加灵活和富有扩展性。
+4. 当某个类除了它的外部类,不再被其他的类使用时。
+
+#### 局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final?
+
+* 局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final呢?它内部原理是什么呢?先看这段代码:
+
+ public class Outer {
+
+ void outMethod(){
+ final int a =10;
+ class Inner {
+ void innerMethod(){
+ System.out.println(a);
+ }
+ }
+ }
+ }
+ 复制代码
+* 以上例子,为什么要加final呢?是因为**生命周期不一致**, 局部变量直接存储在栈中,当方法执行结束后,非final的局部变量就被销毁。而局部内部类对局部变量的引用依然存在,如果局部内部类要调用局部变量时,就会出错。加了final,可以确保局部内部类使用的变量与外层的局部变量区分开,解决了这个问题。
+
+#### 内部类相关,看程序说出运行结果
+
+public class Outer {
+ private int age = 12;
+
+ class Inner {
+ private int age = 13;
+ public void print() {
+ int age = 14;
+ System.out.println("局部变量:" + age);
+ System.out.println("内部类变量:" + this.age);
+ System.out.println("外部类变量:" + Outer.this.age);
+ }
+ }
+
+ public static void main(String[] args) {
+ Outer.Inner in = new Outer().new Inner();
+ in.print();
+ }
+
+}
+复制代码
+
+运行结果:
+
+局部变量:14
+内部类变量:13
+外部类变量:12
+复制代码
+### 重写与重载
+
+#### 构造器(constructor)是否可被重写(override)
+
+* 构造器不能被继承,因此不能被重写,但可以被重载。
+
+#### 重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?
+
+* 方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
+
+* 重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分
+
+* 重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(里氏代换原则);如果父类方法访问修饰符为private则子类中就不是重写。
+
+### 对象相等判断
+
+#### == 和 equals 的区别是什么
+
+* **==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型 == 比较的是值,引用数据类型 == 比较的是内存地址)
+
+* **equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
+
+ * 情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
+
+ * 情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。
+
+ * **举个例子:**
+
+ 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 (重要)
+
+* HashSet如何检查重复
+
+* 两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗?
+
+* hashCode和equals方法的关系
+
+* 面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?”
+
+**hashCode()介绍**
+
+* hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode()函数。
+
+* 散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
+
+**为什么要有 hashCode**
+
+`我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:`
+
+* 当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head first java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
+
+**hashCode()与equals()的相关规定**
+
+* 如果两个对象相等,则hashcode一定也是相同的
+
+* 两个对象相等,对两个对象分别调用equals方法都返回true
+
+* 两个对象有相同的hashcode值,它们也不一定是相等的
+
+`因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖`
+
+`hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)`
+
+#### 对象的相等与指向他们的引用相等,两者有什么不同?
+
+* 对象的相等 比的是内存中存放的内容是否相等而 引用相等 比较的是他们指向的内存地址是否相等。
+
+### 值传递
+
+#### 当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递
+
+* 是值传递。Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的
+
+#### 为什么 Java 中只有值传递
+
+* 首先回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。**按值调用(call by value)表示方法接收的是调用者提供的值,而按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。** 它用来描述各种程序设计语言(不只是Java)中方法参数传递方式。
+
+* **Java程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。**
+
+ * **下面通过 3 个例子来给大家说明**
+
+##### example 1
+
+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 的副本,副本的内容无论怎么修改,都不会影响到原件本身。
+
+`通过上面例子,我们已经知道了一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不一样,请看 example.`
+
+##### example 2
+
+ 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
+
+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包
+
+#### JDK 中常用的包有哪些
+
+* java.lang:这个是系统的基础类;
+* java.io:这里面是所有输入输出有关的类,比如文件操作等;
+* java.nio:为了完善 io 包中的功能,提高 io 包中性能而写的一个新包;
+* java.net:这里面是与网络有关的类;
+* java.util:这个是系统辅助类,特别是集合类;
+* java.sql:这个是数据库操作的类。
+
+#### import java和javax有什么区别
+
+* 刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来说使用。然而随着时间的推移,javax 逐渐的扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包将是太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准API的一部分。
+
+`所以,实际上java和javax没有区别。这都是一个名字。`
+
+## IO流
+
+### java 中 IO 流分为几种?
+
+* 按照流的流向分,可以分为输入流和输出流;
+* 按照操作单元划分,可以划分为字节流和字符流;
+* 按照流的角色划分为节点流和处理流。
+
+`Java Io流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0流的40多个类都是从如下4个抽象类基类中派生出来的。`
+
+* InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
+* OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
+
+`按操作方式分类结构图:`
+
+
+
+`按操作对象分类结构图:`
+
+
+### BIO,NIO,AIO 有什么区别?
+
+* 简答
+
+ * BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
+ * NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
+ * AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。
+* 详细回答
+
+ * **BIO (Blocking I/O):** 同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
+ * **NIO (New I/O):** 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 的非阻塞模式来开发
+ * **AIO (Asynchronous I/O):** AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。
+
+### Files的常用方法都有哪些?
+
+* Files. exists():检测文件路径是否存在。
+* Files. createFile():创建文件。
+* Files. createDirectory():创建文件夹。
+* Files. delete():删除一个文件或目录。
+* Files. copy():复制文件。
+* Files. move():移动文件。
+* Files. size():查看文件个数。
+* Files. read():读取文件。
+* Files. write():写入文件。
+
+## 反射
+
+### 什么是反射机制?
+
+* JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
+
+* 静态编译和动态编译
+
+ * 静态编译:在编译时确定类型,绑定对象
+
+ * 动态编译:运行时确定类型,绑定对象
+
+### 反射机制优缺点
+
+* **优点:** 运行期类型的判断,动态加载类,提高代码灵活度。
+* **缺点:** 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的java代码要慢很多。
+
+### 反射机制的应用场景有哪些?
+
+* 反射是框架设计的灵魂。
+
+* 在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。
+
+* 举例:①我们在使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序;②Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:1) 将程序内所有 XML 或 Properties 配置文件加载入内存中; 2)Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息; 3)使用反射机制,根据这个字符串获得某个类的Class实例; 4)动态配置实例的属性
+
+### Java获取反射的三种方法
+
+1.通过new对象实现反射机制 2.通过路径实现反射机制 3.通过类名实现反射机制
+
+public class Student {
+ private int id;
+ String name;
+ protected boolean sex;
+ public float score;
+}
+
+public class Get {
+ //获取反射机制三种方式
+ public static void main(String[] args) throws ClassNotFoundException {
+ //方式一(通过建立对象)
+ Student stu = new Student();
+ Class classobj1 = stu.getClass();
+ System.out.println(classobj1.getName());
+ //方式二(所在通过路径-相对路径)
+ Class classobj2 = Class.forName("fanshe.Student");
+ System.out.println(classobj2.getName());
+ //方式三(通过类名)
+ Class classobj3 = Student.class;
+ System.out.println(classobj3.getName());
+ }
+}
+复制代码
+## 常用API
+
+### String相关
+
+#### 字符型常量和字符串常量的区别
+
+1. 形式上: 字符常量是单引号引起的一个字符 字符串常量是双引号引起的若干个字符
+2. 含义上: 字符常量相当于一个整形值(ASCII值),可以参加表达式运算 字符串常量代表一个地址值(该字符串在内存中存放位置)
+3. 占内存大小 字符常量只占一个字节 字符串常量占若干个字节(至少一个字符结束标志)
+
+#### 什么是字符串常量池?
+
+* 字符串常量池位于堆内存中,专门用来存储字符串常量,可以提高内存的使用率,避免开辟多块空间存储相同的字符串,在创建字符串时 JVM 会首先检查字符串常量池,如果该字符串已经存在池中,则返回它的引用,如果不存在,则实例化一个字符串放到池中,并返回其引用。
+
+#### String 是最基本的数据类型吗
+
+* 不是。Java 中的基本数据类型只有 8 个 :byte、short、int、long、float、double、char、boolean;除了基本类型(primitive type),剩下的都是引用类型(referencetype),Java 5 以后引入的枚举类型也算是一种比较特殊的引用类型。
+
+`这是很基础的东西,但是很多初学者却容易忽视,Java 的 8 种基本数据类型中不包括 String,基本数据类型中用来描述文本数据的是 char,但是它只能表示单个字符,比如 ‘a’,‘好’ 之类的,如果要描述一段文本,就需要用多个 char 类型的变量,也就是一个 char 类型数组,比如“你好” 就是长度为2的数组 char\[\] chars = {‘你’,‘好’};`
+
+`但是使用数组过于麻烦,所以就有了 String,String 底层就是一个 char 类型的数组,只是使用的时候开发者不需要直接操作底层数组,用更加简便的方式即可完成对字符串的使用。`
+
+#### String有哪些特性
+
+* 不变性:String 是只读字符串,是一个典型的 immutable 对象,对它进行任何操作,其实都是创建一个新的对象,再把引用指向该对象。不变模式的主要作用在于当一个对象需要被多线程共享并频繁访问时,可以保证数据的一致性。
+
+* 常量池优化:String 对象创建之后,会在字符串常量池中进行缓存,如果下次创建同样的对象时,会直接返回缓存的引用。
+
+* final:使用 final 来定义 String 类,表示 String 类不能被继承,提高了系统的安全性。
+
+#### String为什么是不可变的吗?
+
+* 简单来说就是String类利用了final修饰的char类型数组存储字符,源码如下图所以:
+
+ /** The value is used for character storage. */ private final char value[];
+
+#### String真的是不可变的吗?
+
+* 我觉得如果别人问这个问题的话,回答不可变就可以了。 下面只是给大家看两个有代表性的例子:
+
+**1 String不可变但不代表引用不可以变**
+
+String str = "Hello";
+str = str + " World";
+System.out.println("str=" + str);
+复制代码
+
+* 结果:
+
+ str=Hello World
+
+* 解析:
+
+* 实际上,原来String的内容是不变的,只是str由原来指向"Hello"的内存地址转为指向"Hello World"的内存地址而已,也就是说多开辟了一块内存区域给"Hello World"字符串。
+
+**2.通过反射是可以修改所谓的“不可变”对象**
+
+// 创建字符串"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引用改变数组的结构。但是一般我们不会这么做,这里只是简单提一下有这个东西。
+
+#### 是否可以继承 String 类
+
+* String 类是 final 类,不可以被继承。
+
+#### String str="i"与 String str=new String(“i”)一样吗?
+
+* 不一样,因为内存的分配方式不一样。String str="i"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。
+
+#### String s = new String(“xyz”);创建了几个字符串对象
+
+* 两个对象,一个是静态区的"xyz",一个是用new创建在堆上的对象。
+
+ String str1 = "hello"; //str1指向静态区 String str2 = new String("hello"); //str2指向堆上的对象 String str3 = "hello"; String str4 = new String("hello"); System.out.println(str1.equals(str2)); //true System.out.println(str2.equals(str4)); //true System.out.println(str1 == str3); //true System.out.println(str1 == str2); //false System.out.println(str2 == str4); //false System.out.println(str2 == "hello"); //false str2 = str1; System.out.println(str2 == "hello"); //true
+
+#### 如何将字符串反转?
+
+* 使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。
+
+* 示例代码:
+
+ // StringBuffer reverse StringBuffer stringBuffer = new StringBuffer(); stringBuffer. append("abcdefg"); System. out. println(stringBuffer. reverse()); // gfedcba // StringBuilder reverse StringBuilder stringBuilder = new StringBuilder(); stringBuilder. append("abcdefg"); System. out. println(stringBuilder. reverse()); // gfedcba
+
+#### 数组有没有 length()方法?String 有没有 length()方法
+
+* 数组没有 length()方法 ,有 length 的属性。String 有 length()方法。JavaScript中,获得字符串的长度是通过 length 属性得到的,这一点容易和 Java 混淆。
+
+#### String 类的常用方法都有那些?
+
+* indexOf():返回指定字符的索引。
+* charAt():返回指定索引处的字符。
+* replace():字符串替换。
+* trim():去除字符串两端空白。
+* split():分割字符串,返回一个分割后的字符串数组。
+* getBytes():返回字符串的 byte 类型数组。
+* length():返回字符串长度。
+* toLowerCase():将字符串转成小写字母。
+* toUpperCase():将字符串转成大写字符。
+* substring():截取字符串。
+* equals():字符串比较。
+
+#### 在使用 HashMap 的时候,用 String 做 key 有什么好处?
+
+* HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置,因为字符串是不可变的,所以当创建字符串时,它的 hashcode 被缓存下来,不需要再次计算,所以相比于其他对象更快。
+
+#### String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的
+
+**可变性**
+
+* String类中使用字符数组保存字符串,private final char value[],所以string对象是不可变的。StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[] value,这两种对象都是可变的。
+
+**线程安全性**
+
+* String中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。
+
+**性能**
+
+* 每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StirngBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
+
+**对于三者使用的总结**
+
+* 如果要操作少量的数据用 = String
+
+* 单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
+
+* 多线程操作字符串缓冲区 下操作大量数据 = StringBuffer
+
+### Date相关
+
+### 包装类相关
+
+#### 自动装箱与拆箱
+
+* **装箱**:将基本类型用它们对应的引用类型包装起来;
+
+* **拆箱**:将包装类型转换为基本数据类型;
+
+#### int 和 Integer 有什么区别
+
+* Java 是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java 为每一个基本数据类型都引入了对应的包装类型(wrapper class),int 的包装类就是 Integer,从 Java 5 开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
+
+* Java 为每个原始类型提供了包装类型:
+
+ * 原始类型: boolean,char,byte,short,int,long,float,double
+
+ * 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
+
+#### Integer a= 127 与 Integer b = 127相等吗
+
+* 对于对象引用类型:==比较的是对象的内存地址。
+* 对于基本数据类型:==比较的是值。
+
+`如果整型字面量的值在-128到127之间,那么自动装箱时不会new新的Integer对象,而是直接引用常量池中的Integer对象,超过范围 a1==b1的结果是false`
+
+public static void main(String[] args) {
+ Integer a = new Integer(3);
+ Integer b = 3; // 将3自动装箱成Integer类型
+ int c = 3;
+ System.out.println(a == b); // false 两个引用没有引用同一对象
+ System.out.println(a == c); // true a自动拆箱成int类型再和c比较
+ System.out.println(b == c); // true
+
+ Integer a1 = 128;
+ Integer b1 = 128;
+ System.out.println(a1 == b1); // false
+
+ Integer a2 = 127;
+ Integer b2 = 127;
+ System.out.println(a2 == b2); // true
+}
+
+作者:小杰要吃蛋
+链接:https://juejin.cn/post/6844904127059738631
+来源:稀土掘金
+著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
\ No newline at end of file
diff --git "a/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\345\271\266\345\217\221.md" "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\345\271\266\345\217\221.md"
new file mode 100644
index 0000000..50d9a6a
--- /dev/null
+++ "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\345\271\266\345\217\221.md"
@@ -0,0 +1,1478 @@
+https://juejin.cn/post/6844904119338024974#heading-17
+
+## 基础知识
+
+#### 为什么要使用并发编程
+
+* 提升多核CPU的利用率:一般来说一台主机上的会有多个CPU核心,我们可以创建多个线程,理论上讲操作系统可以将多个线程分配给不同的CPU去执行,每个CPU执行一个线程,这样就提高了CPU的使用效率,如果使用单线程就只能有一个CPU核心被使用。
+
+* 比如当我们在网上购物时,为了提升响应速度,需要拆分,减库存,生成订单等等这些操作,就可以进行拆分利用多线程的技术完成。面对复杂业务模型,并行程序会比串行程序更适应业务需求,而并发编程更能吻合这种业务拆分 。
+
+* 简单来说就是:
+
+ * 充分利用多核CPU的计算能力;
+ * 方便进行业务拆分,提升应用性能
+
+#### 多线程应用场景
+
+* 例如: 迅雷多线程下载、数据库连接池、分批发送短信等。
+
+#### 并发编程有什么缺点
+
+* 并发编程的目的就是为了能提高程序的执行效率,提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如:内存泄漏、上下文切换、线程安全、死锁等问题。
+
+#### 并发编程三个必要因素是什么?
+
+* 原子性:原子,即一个不可再被分割的颗粒。原子性指的是一个或多个操作要么全部执行成功要么全部执行失败。
+* 可见性:一个线程对共享变量的修改,另一个线程能够立刻看到。(synchronized,volatile)
+* 有序性:程序执行的顺序按照代码的先后顺序执行。(处理器可能会对指令进行重排序)
+
+#### 在 Java 程序中怎么保证多线程的运行安全?
+
+* 出现线程安全问题的原因一般都是三个原因:
+
+ * 线程切换带来的原子性问题 解决办法:使用多线程之间同步synchronized或使用锁(lock)。
+
+ * 缓存导致的可见性问题 解决办法:synchronized、volatile、LOCK,可以解决可见性问题
+
+ * 编译优化带来的有序性问题 解决办法:Happens-Before 规则可以解决有序性问题
+
+#### 并行和并发有什么区别?
+
+* 并发:多个任务在同一个 CPU 核上,按细分的时间片轮流(交替)执行,从逻辑上来看那些任务是同时执行。
+* 并行:单位时间内,多个处理器或多核处理器同时处理多个任务,是真正意义上的“同时进行”。
+* 串行:有n个任务,由一个线程按顺序执行。由于任务、方法都在一个线程执行所以不存在线程不安全情况,也就不存在临界区的问题。
+
+**做一个形象的比喻:**
+
+* 并发 = 俩个人用一台电脑。
+
+* 并行 = 俩个人分配了俩台电脑。
+
+* 串行 = 俩个人排队使用一台电脑。
+
+#### 什么是多线程
+
+* 多线程:多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务。
+
+#### 多线程的好处
+
+* 可以提高 CPU 的利用率。在多线程程序中,一个线程必须等待的时候,CPU 可以运行其它的线程而不是等待,这样就大大提高了程序的效率。也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
+
+#### 多线程的劣势:
+
+* 线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;
+
+* 多线程需要协调和管理,所以需要 CPU 时间跟踪线程;
+
+* 线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题。
+
+#### 线程和进程区别
+
+* 什么是线程和进程?
+
+ * 进程
+
+ 一个在内存中运行的应用程序。 每个正在系统上运行的程序都是一个进程
+
+ * 线程
+
+ 进程中的一个执行任务(控制单元), 它负责在程序里独立执行。
+
+`一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。`
+
+* 进程与线程的区别
+
+ * 根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位
+
+ * 资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
+
+ * 包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。
+
+ * 内存分配:同一进程的线程共享本进程的地址空间和资源,而进程与进程之间的地址空间和资源是相互独立的
+
+ * 影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃有可能导致整个进程都死掉。所以多进程要比多线程健壮。
+
+ * 执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行
+
+#### 什么是上下文切换?
+
+* 多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。
+
+* 概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。
+
+* 上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。
+
+* Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。
+
+#### 守护线程和用户线程有什么区别呢?
+
+* 用户 (User) 线程:运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程
+* 守护 (Daemon) 线程:运行在后台,为其他前台线程服务。也可以说守护线程是 JVM 中非守护线程的 “佣人”。一旦所有用户线程都结束运行,守护线程会随 JVM 一起结束工作
+
+#### 如何在 Windows 和 Linux 上查找哪个线程cpu利用率最高?
+
+* windows上面用任务管理器看,linux下可以用 top 这个工具看。
+ * 找出cpu耗用厉害的进程pid, 终端执行top命令,然后按下shift+p (shift+m是找出消耗内存最高)查找出cpu利用最厉害的pid号
+ * 根据上面第一步拿到的pid号,top -H -p pid 。然后按下shift+p,查找出cpu利用率最厉害的线程号,比如top -H -p 1328
+ * 将获取到的线程号转换成16进制,去百度转换一下就行
+ * 使用jstack工具将进程信息打印输出,jstack pid号 > /tmp/t.dat,比如jstack 31365 > /tmp/t.dat
+ * 编辑/tmp/t.dat文件,查找线程号对应的信息
+
+`或者直接使用JDK自带的工具查看“jconsole” 、“visualVm”,这都是JDK自带的,可以直接在JDK的bin目录下找到直接使用`
+
+#### 什么是线程死锁
+
+* 死锁是指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程(线程)称为死锁进程(线程)。
+* 多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
+* 如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。
+
+
+#### 形成死锁的四个必要条件是什么
+
+* 互斥条件:在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,就只能等待,直至占有资源的进程用毕释放。
+* 占有且等待条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
+* 不可抢占条件:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
+* 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。(比如一个进程集合,A在等B,B在等C,C在等A)
+
+#### 如何避免线程死锁
+
+1. 避免一个线程同时获得多个锁
+2. 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
+3. 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制
+
+#### 创建线程的四种方式
+
+* 继承 Thread 类;
+
+```java
+public class MyThread extends Thread {
+ @Override
+ public void run() {
+ System.out.println(Thread.currentThread().getName() + " run()方法正在执行...");
+ }
+```
+* 实现 Runnable 接口;
+
+```java
+public class MyRunnable implements Runnable {
+ @Override
+ public void run() {
+ System.out.println(Thread.currentThread().getName() + " run()方法执行中...");
+ }
+```
+* 实现 Callable 接口;
+
+```java
+public class MyCallable implements Callable {
+ @Override
+ public Integer call() {
+ System.out.println(Thread.currentThread().getName() + " call()方法执行中...");
+ return 1;
+ }
+```
+* 使用匿名内部类方式
+
+```java
+public class CreateRunnable {
+ public static void main(String[] args) {
+ //创建多线程创建开始
+ Thread thread = new Thread(new Runnable() {
+ public void run() {
+ for (int i = 0; i < 10; i++) {
+ System.out.println("i:" + i);
+ }
+ }
+ });
+ thread.start();
+ }
+ }
+```
+
+#### 说一下 runnable 和 callable 有什么区别
+
+**相同点:**
+
+* 都是接口
+* 都可以编写多线程程序
+* 都采用Thread.start()启动线程
+
+**主要区别:**
+
+* Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
+* Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息 注:Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
+
+#### 线程的 run()和 start()有什么区别?
+
+* 每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,run()方法称为线程体。通过调用Thread类的start()方法来启动一个线程。
+
+* start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。run() 可以重复调用,而 start() 只能调用一次。
+
+* start()方法来启动一个线程,真正实现了多线程运行。调用start()方法无需等待run方法体代码执行完毕,可以直接继续执行其他的代码; 此时线程是处于就绪状态,并没有运行。 然后通过此Thread类调用方法run()来完成其运行状态, run()方法运行结束, 此线程终止。然后CPU再调度其它线程。
+
+* run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。
+
+#### 为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?
+
+这是另一个非常经典的 java 多线程面试问题,而且在面试中会经常被问到。很简单,但是很多人都会答不上来!
+
+* new 一个 Thread,线程进入了新建状态。调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到`时间片`后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。
+
+* 而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
+
+总结: 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。
+
+#### 什么是 Callable 和 Future?
+
+* Callable 接口类似于 Runnable,从名字就可以看出来了,但是 Runnable 不会返回结果,并且无法抛出返回结果的异常,而 Callable 功能更强大一些,被线程执行后,可以返回值,这个返回值可以被 Future 拿到,也就是说,Future 可以拿到异步执行任务的返回值。
+
+* Future 接口表示异步任务,是一个可能还没有完成的异步任务的结果。所以说 Callable用于产生结果,Future 用于获取结果。
+
+#### 什么是 FutureTask
+
+* FutureTask 表示一个异步运算的任务。FutureTask 里面可以传入一个 Callable 的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。只有当运算完成的时候结果才能取回,如果运算尚未完成 get 方法将会阻塞。一个 FutureTask 对象可以对调用了 Callable 和 Runnable 的对象进行包装,由于 FutureTask 也是Runnable 接口的实现类,所以 FutureTask 也可以放入线程池中。
+
+#### 线程的状态
+
+
+
+* 新建(new):新创建了一个线程对象。
+
+* 就绪(可运行状态)(runnable):线程对象创建后,当调用线程对象的 start()方法,该线程处于就绪状态,等待被线程调度选中,获取cpu的使用权。
+
+* 运行(running):可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
+
+* 阻塞(block):处于运行状态中的线程由于某种原因,暂时放弃对 CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被 CPU 调用以进入到运行状态。
+
+ * 阻塞的情况分三种:
+ * (一). 等待阻塞:运行状态中的线程执行 wait()方法,JVM会把该线程放入等待队列(waitting queue)中,使本线程进入到等待阻塞状态;
+ * (二). 同步阻塞:线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用),,则JVM会把该线程放入锁池(lock pool)中,线程会进入同步阻塞状态;
+ * (三). 其他阻塞: 通过调用线程的 sleep()或 join()或发出了 I/O 请求时,线程会进入到阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。
+* 死亡(dead)(结束):线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
+
+#### Java 中用到的线程调度算法是什么?
+
+* 计算机通常只有一个 CPU,在任意时刻只能执行一条机器指令,每个线程只有获得CPU 的使用权才能执行指令。所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得 CPU 的使用权,分别执行各自的任务。在运行池中,会有多个处于就绪状态的线程在等待 CPU,JAVA 虚拟机的一项任务就是负责线程的调度,线程调度是指按照特定机制为多个线程分配 CPU 的使用权。(Java是由JVM中的线程计数器来实现线程调度)
+
+* 有两种调度模型:分时调度模型和抢占式调度模型。
+
+ * 分时调度模型是指让所有的线程轮流获得 cpu 的使用权,并且平均分配每个线程占用的 CPU 的时间片这个也比较好理解。
+
+ * Java虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃 CPU。
+
+#### 线程的调度策略
+
+`线程调度器选择优先级最高的线程运行,但是,如果发生以下情况,就会终止线程的运行:`
+
+* (1)线程体中调用了 yield 方法让出了对 cpu 的占用权利
+
+* (2)线程体中调用了 sleep 方法使线程进入睡眠状态
+
+* (3)线程由于 IO 操作受到阻塞
+
+* (4)另外一个更高优先级线程出现
+
+* (5)在支持时间片的系统中,该线程的时间片用完
+
+#### 什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing )?
+
+* 线程调度器是一个操作系统服务,它负责为 Runnable 状态的线程分配 CPU 时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。
+
+* 时间分片是指将可用的 CPU 时间分配给可用的 Runnable 线程的过程。分配 CPU 时间可以基于线程优先级或者线程等待的时间。
+
+* 线程调度并不受到 Java 虚拟机控制,所以由应用程序来控制它是更好的选择(也就是说不要让你的程序依赖于线程的优先级)。
+
+#### 请说出与线程同步以及线程调度相关的方法。
+
+* (1) wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
+
+* (2)sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理 InterruptedException 异常;
+
+* (3)notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与优先级无关;
+
+* (4)notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
+
+#### sleep() 和 wait() 有什么区别?
+
+`两者都可以暂停线程的执行`
+
+* 类的不同:sleep() 是 Thread线程类的静态方法,wait() 是 Object类的方法。
+* 是否释放锁:sleep() 不释放锁;wait() 释放锁。
+* 用途不同:Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。
+* 用法不同:wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用wait(long timeout)超时后线程会自动苏醒。
+
+#### 你是如何调用 wait() 方法的?使用 if 块还是循环?为什么?
+
+* 处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。
+
+* wait() 方法应该在循环调用,因为当线程获取到 CPU 开始执行的时候,其他条件可能还没有满足,所以在处理前,循环检测条件是否满足会更好。下面是一段标准的使用 wait 和 notify 方法的代码:
+
+synchronized (monitor) {
+ // 判断条件谓词是否得到满足
+ while(!locked) {
+ // 等待唤醒
+ monitor.wait();
+ }
+ // 处理其他的业务逻辑
+}
+复制代码
+#### 为什么线程通信的方法 wait(), notify()和 notifyAll()被定义在 Object 类里?
+
+* 因为Java所有类的都继承了Object,Java想让任何对象都可以作为锁,并且 wait(),notify()等方法用于等待对象的锁或者唤醒线程,在 Java 的线程中并没有可供任何对象使用的锁,所以任意对象调用方法一定定义在Object类中。
+
+* 有的人会说,既然是线程放弃对象锁,那也可以把wait()定义在Thread类里面啊,新定义的线程继承于Thread类,也不需要重新定义wait()方法的实现。然而,这样做有一个非常大的问题,一个线程完全可以持有很多锁,你一个线程放弃锁的时候,到底要放弃哪个锁?当然了,这种设计并不是不能实现,只是管理起来更加复杂。
+
+#### 为什么 wait(), notify()和 notifyAll()必须在同步方法或者同步块中被调用?
+
+* 当一个线程需要调用对象的 wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的 notify()方法。同样的,当一个线程需要调用对象的 notify()方法时,它会释放这个对象的锁,以便其他在等待的线程就可以得到这个对象锁。由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用。
+
+#### Thread 类中的 yield 方法有什么作用?
+
+* 使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。
+
+* 当前线程到了就绪状态,那么接下来哪个线程会从就绪状态变成执行状态呢?可能是当前线程,也可能是其他线程,看系统的分配了。
+
+#### 为什么 Thread 类的 sleep()和 yield ()方法是静态的?
+
+* Thread 类的 sleep()和 yield()方法将在当前正在执行的线程上运行。所以在其他处于等待状态的线程上调用这些方法是没有意义的。这就是为什么这些方法是静态的。它们可以在当前正在执行的线程中工作,并避免程序员错误的认为可以在其他非运行线程调用这些方法。
+
+#### 线程的 sleep()方法和 yield()方法有什么区别?
+
+* (1) sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
+
+* (2) 线程执行 sleep()方法后转入阻塞(blocked)状态,而执行 yield()方法后转入就绪(ready)状态;
+
+* (3)sleep()方法声明抛出 InterruptedException,而 yield()方法没有声明任何异常;
+
+* (4)sleep()方法比 yield()方法(跟操作系统 CPU 调度相关)具有更好的可移植性,通常不建议使用yield()方法来控制并发线程的执行。
+
+#### 如何停止一个正在运行的线程?
+
+* 在java中有以下3种方法可以终止正在运行的线程:
+ * 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
+ * 使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的方法。
+ * 使用interrupt方法中断线程。
+
+#### Java 中 interrupted 和 isInterrupted 方法的区别?
+
+* interrupt:用于中断线程。调用该方法的线程的状态为将被置为”中断”状态。
+
+ 注意:线程中断仅仅是置线程的中断状态位,不会停止线程。需要用户自己去监视线程的状态为并做处理。支持线程中断的方法(也就是线程中断后会抛出interruptedException 的方法)就是在监视线程的中断状态,一旦线程的中断状态被置为“中断状态”,就会抛出中断异常。
+
+* interrupted:是静态方法,查看当前中断信号是true还是false并且清除中断信号。如果一个线程被中断了,第一次调用 interrupted 则返回 true,第二次和后面的就返回 false 了。
+
+* isInterrupted:是可以返回当前中断信号是true还是false,与interrupt最大的差别
+
+#### 什么是阻塞式方法?
+
+* 阻塞式方法是指程序会一直等待该方法完成期间不做其他事情,ServerSocket 的accept()方法就是一直等待客户端连接。这里的阻塞是指调用结果返回之前,当前线程会被挂起,直到得到结果之后才会返回。此外,还有异步和非阻塞式方法在任务完成前就返回。
+
+#### Java 中你怎样唤醒一个阻塞的线程?
+
+* 首先 ,wait()、notify() 方法是针对对象的,调用任意对象的 wait()方法都将导致线程阻塞,阻塞的同时也将释放该对象的锁,相应地,调用任意对象的 notify()方法则将随机解除该对象阻塞的线程,但它需要重新获取该对象的锁,直到获取成功才能往下执行;
+
+* 其次,wait、notify 方法必须在 synchronized 块或方法中被调用,并且要保证同步块或方法的锁对象与调用 wait、notify 方法的对象是同一个,如此一来在调用 wait 之前当前线程就已经成功获取某对象的锁,执行 wait 阻塞后当前线程就将之前获取的对象锁释放。
+
+#### notify() 和 notifyAll() 有什么区别?
+
+* 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
+
+* notifyAll() 会唤醒所有的线程,notify() 只会唤醒一个线程。
+
+* notifyAll() 调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而 notify()只会唤醒一个线程,具体唤醒哪一个线程由虚拟机控制。
+
+#### 如何在两个线程间共享数据?
+
+* 在两个线程间共享变量即可实现共享。
+
+`一般来说,共享变量要求变量本身是线程安全的,然后在线程内使用的时候,如果有对共享变量的复合操作,那么也得保证复合操作的线程安全性。`
+
+#### Java 如何实现多线程之间的通讯和协作?
+
+* 可以通过中断 和 共享变量的方式实现线程间的通讯和协作
+
+* 比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。这种互相通信的过程就是线程间的协作。
+
+* Java中线程通信协作的最常见方式:
+
+ * 一.syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()
+
+ * 二.ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()
+
+* 线程间直接的数据交换:
+
+ * 三.通过管道进行线程间通信:字节流、字符流
+
+#### 同步方法和同步块,哪个是更好的选择?
+
+* 同步块是更好的选择,因为它不会锁住整个对象(当然你也可以让它锁住整个对象)。同步方法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这通常会导致他们停止执行并需要等待获得这个对象上的锁。
+
+* 同步块更要符合开放调用的原则,只在需要锁住的代码块锁住相应的对象,这样从侧面来说也可以避免死锁。
+
+`请知道一条原则:同步的范围越小越好。`
+
+#### 什么是线程同步和线程互斥,有哪几种实现方式?
+
+* 当一个线程对共享的数据进行操作时,应使之成为一个”原子操作“,即在没有完成相关操作之前,不允许其他线程打断它,否则,就会破坏数据的完整性,必然会得到错误的处理结果,这就是线程的同步。
+
+* 在多线程应用中,考虑不同线程之间的数据同步和防止死锁。当两个或多个线程之间同时等待对方释放资源的时候就会形成线程之间的死锁。为了防止死锁的发生,需要通过同步来实现线程安全。
+
+* 线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步。
+
+* 线程间的同步方法大体可分为两类:用户模式和内核模式。顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态,而用户模式就是不需要切换到内核态,只在用户态完成操作。
+
+* 用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。内核模式下的方法有:事件,信号量,互斥量。
+
+* 实现线程同步的方法
+
+ * 同步代码方法:sychronized 关键字修饰的方法
+
+ * 同步代码块:sychronized 关键字修饰的代码块
+
+ * 使用特殊变量域volatile实现线程同步:volatile关键字为域变量的访问提供了一种免锁机制
+
+ * 使用重入锁实现线程同步:reentrantlock类是可冲入、互斥、实现了lock接口的锁他与sychronized方法具有相同的基本行为和语义
+
+#### 在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪种级别的同步?
+
+* 在 java 虚拟机中,监视器和锁在Java虚拟机中是一块使用的。监视器监视一块同步代码块,确保一次只有一个线程执行同步代码块。每一个监视器都和一个对象引用相关联。线程在获取锁之前不允许执行同步代码。
+
+* 一旦方法或者代码块被 synchronized 修饰,那么这个部分就放入了监视器的监视区域,确保一次只能有一个线程执行该部分的代码,线程在获取锁之前不允许执行该部分的代码
+
+* 另外 java 还提供了显式监视器( Lock )和隐式监视器( synchronized )两种锁方案
+
+#### 如果你提交任务时,线程池队列已满,这时会发生什么
+
+* 有俩种可能:
+
+ (1)如果使用的是无界队列 LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为 LinkedBlockingQueue 可以近乎认为是一个无穷大的队列,可以无限存放任务
+
+ (2)如果使用的是有界队列比如 ArrayBlockingQueue,任务首先会被添加到ArrayBlockingQueue 中,ArrayBlockingQueue 满了,会根据maximumPoolSize 的值增加线程数量,如果增加了线程数量还是处理不过来,ArrayBlockingQueue 继续满,那么则会使用拒绝策略RejectedExecutionHandler 处理满了的任务,默认是 AbortPolicy
+
+#### 什么叫线程安全?servlet 是线程安全吗?
+
+* 线程安全是编程中的术语,指某个方法在多线程环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。
+
+* Servlet 不是线程安全的,servlet 是单实例多线程的,当多个线程同时访问同一个方法,是不能保证共享变量的线程安全性的。
+
+* Struts2 的 action 是多实例多线程的,是线程安全的,每个请求过来都会 new 一个新的 action 分配给这个请求,请求完成后销毁。
+
+* SpringMVC 的 Controller 是线程安全的吗?不是的,和 Servlet 类似的处理流程。
+
+* Struts2 好处是不用考虑线程安全问题;Servlet 和 SpringMVC 需要考虑线程安全问题,但是性能可以提升不用处理太多的 gc,可以使用 ThreadLocal 来处理多线程的问题。
+
+#### 在 Java 程序中怎么保证多线程的运行安全?
+
+* 方法一:使用安全类,比如 java.util.concurrent 下的类,使用原子类AtomicInteger
+
+* 方法二:使用自动锁 synchronized。
+
+* 方法三:使用手动锁 Lock。
+
+* 手动锁 Java 示例代码如下:
+
+```java
+Lock lock = new ReentrantLock();
+ lock. lock();
+ try {
+ System. out. println("获得锁");
+ } catch (Exception e) {
+ // TODO: handle exception
+ } finally {
+ System. out. println("释放锁");
+ lock. unlock();
+ }
+```
+
+#### 你对线程优先级的理解是什么?
+
+* 每一个线程都是有优先级的,一般来说,高优先级的线程在运行时会具有优先权,但这依赖于线程调度的实现,这个实现是和操作系统相关的(OS dependent)。我们可以定义线程的优先级,但是这并不能保证高优先级的线程会在低优先级的线程前执行。线程优先级是一个 int 变量(从 1-10),1 代表最低优先级,10 代表最高优先级。
+
+* Java 的线程优先级调度会委托给操作系统去处理,所以与具体的操作系统优先级有关,如非特别需要,一般无需设置线程优先级。
+
+* 当然,如果你真的想设置优先级可以通过setPriority()方法设置,但是设置了不一定会该变,这个是不准确的
+
+#### 线程类的构造方法、静态块是被哪个线程调用的
+
+* 这是一个非常刁钻和狡猾的问题。请记住:线程类的构造方法、静态块是被 new这个线程类所在的线程所调用的,而 run 方法里面的代码才是被线程自身所调用的。
+
+* 如果说上面的说法让你感到困惑,那么我举个例子,假设 Thread2 中 new 了Thread1,main 函数中 new 了 Thread2,那么:
+
+(1)Thread2 的构造方法、静态块是 main 线程调用的,Thread2 的 run()方法是Thread2 自己调用的
+
+(2)Thread1 的构造方法、静态块是 Thread2 调用的,Thread1 的 run()方法是Thread1 自己调用的
+
+#### Java 中怎么获取一份线程 dump 文件?你如何在 Java 中获取线程堆栈?
+
+* Dump文件是进程的内存镜像。可以把程序的执行状态通过调试器保存到dump文件中。
+
+* 在 Linux 下,你可以通过命令 kill -3 PID (Java 进程的进程 ID)来获取 Java应用的 dump 文件。
+
+* 在 Windows 下,你可以按下 Ctrl + Break 来获取。这样 JVM 就会将线程的 dump 文件打印到标准输出或错误文件中,它可能打印在控制台或者日志文件中,具体位置依赖应用的配置。
+
+#### 一个线程运行时发生异常会怎样?
+
+* 如果异常没有被捕获该线程将会停止执行。Thread.UncaughtExceptionHandler是用于处理未捕获异常造成线程突然中断情况的一个内嵌接口。当一个未捕获异常将造成线程中断的时候,JVM 会使用 Thread.getUncaughtExceptionHandler()来查询线程的 UncaughtExceptionHandler 并将线程和异常作为参数传递给 handler 的 uncaughtException()方法进行处理。
+
+#### Java 线程数过多会造成什么异常?
+
+* 线程的生命周期开销非常高
+
+* 消耗过多的 CPU
+
+ 资源如果可运行的线程数量多于可用处理器的数量,那么有线程将会被闲置。大量空闲的线程会占用许多内存,给垃圾回收器带来压力,而且大量的线程在竞争 CPU资源时还将产生其他性能的开销。
+
+* 降低稳定性JVM
+
+ 在可创建线程的数量上存在一个限制,这个限制值将随着平台的不同而不同,并且承受着多个因素制约,包括 JVM 的启动参数、Thread 构造函数中请求栈的大小,以及底层操作系统对线程的限制等。如果破坏了这些限制,那么可能抛出OutOfMemoryError 异常。
+
+#### 多线程的常用方法
+
+| 方法 名 | 描述 |
+| --- | --- |
+| sleep() | 强迫一个线程睡眠N毫秒 |
+| isAlive() | 判断一个线程是否存活。 |
+| join() | 等待线程终止。 |
+| activeCount() | 程序中活跃的线程数。 |
+| enumerate() | 枚举程序中的线程。 |
+| currentThread() | 得到当前线程。 |
+| isDaemon() | 一个线程是否为守护线程。 |
+| setDaemon() | 设置一个线程为守护线程。 |
+| setName() | 为线程设置一个名称。 |
+| wait() | 强迫一个线程等待。 |
+| notify() | 通知一个线程继续运行。 |
+| setPriority() | 设置一个线程的优先级。 |
+
+## 并发理论
+
+#### Java中垃圾回收有什么目的?什么时候进行垃圾回收?
+
+* 垃圾回收是在内存中存在没有引用的对象或超过作用域的对象时进行的。
+
+* 垃圾回收的目的是识别并且丢弃应用不再使用的对象来释放和重用资源。
+
+#### 线程之间如何通信及线程之间如何同步
+
+* 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步。通信是指线程之间以如何来交换信息。一般线程之间的通信机制有两种:共享内存和消息传递。
+
+* Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式进行,整个通信过程对程序员完全透明。如果编写多线程程序的Java程序员不理解隐式进行的线程之间通信的工作机制,很可能会遇到各种奇怪的内存可见性问题。
+
+#### Java内存模型
+
+* 共享内存模型指的就是Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入时,能对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。
+
+
+
+* 从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:
+ 1. 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
+ 2. 然后,线程B到主内存中去读取线程A之前已更新过的共享变量。
+
+**下面通过示意图来说明线程之间的通信**
+
+
+
+* 总结:什么是Java内存模型:java内存模型简称jmm,定义了一个线程对另一个线程可见。共享变量存放在主内存中,每个线程都有自己的本地内存,当多个线程同时访问一个数据的时候,可能本地内存没有及时刷新到主内存,所以就会发生线程安全问题。
+
+#### 如果对象的引用被置为null,垃圾收集器是否会立即释放对象占用的内存?
+
+* 不会,在下一个垃圾回调周期中,这个对象将是被可回收的。
+
+* 也就是说并不会立即被垃圾收集器立刻回收,而是在下一次垃圾回收时才会释放其占用的内存。
+
+#### finalize()方法什么时候被调用?析构函数(finalization)的目的是什么?
+
+* 1.垃圾回收器(garbage colector)决定回收某对象时,就会运行该对象的finalize()方法; finalize是Object类的一个方法,该方法在Object类中的声明protected void finalize() throws Throwable { } 在垃圾回收器执行时会调用被回收对象的finalize()方法,可以覆盖此方法来实现对其资源的回收。注意:一旦垃圾回收器准备释放对象占用的内存,将首先调用该对象的finalize()方法,并且下一次垃圾回收动作发生时,才真正回收对象占用的内存空间
+
+* 1. GC本来就是内存回收了,应用还需要在finalization做什么呢? 答案是大部分时候,什么都不用做(也就是不需要重载)。只有在某些很特殊的情况下,比如你调用了一些native的方法(一般是C写的),可以要在finaliztion里去调用C的释放函数。
+ * Finalizetion主要用来释放被对象占用的资源(不是指内存,而是指其他资源,比如文件(File Handle)、端口(ports)、数据库连接(DB Connection)等)。然而,它不能真正有效地工作。
+
+#### 什么是重排序
+
+* 程序执行的顺序按照代码的先后顺序执行。
+* 一般来说处理器为了提高程序运行效率,可能会对输入代码进行优化,进行重新排序(重排序),它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
+
+int a = 5; //语句1
+int r = 3; //语句2
+a = a + 2; //语句3
+r = a*a; //语句4
+复制代码
+
+* 则因为重排序,他还可能执行顺序为(这里标注的是语句的执行顺序) 2-1-3-4,1-3-2-4 但绝不可能 2-1-4-3,因为这打破了依赖关系。
+* 显然重排序对单线程运行是不会有任何问题,但是多线程就不一定了,所以我们在多线程编程时就得考虑这个问题了。
+
+#### 重排序实际执行的指令步骤
+
+
+
+1. 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
+2. 指令级并行的重排序。现代处理器采用了指令级并行技术(ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
+3. 内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
+
+* 这些重排序对于单线程没问题,但是多线程都可能会导致多线程程序出现内存可见性问题。
+
+#### 重排序遵守的规则
+
+* as-if-serial:
+ 1. 不管怎么排序,结果不能改变
+ 2. 不存在数据依赖的可以被编译器和处理器重排序
+ 3. 一个操作依赖两个操作,这两个操作如果不存在依赖可以重排序
+ 4. 单线程根据此规则不会有问题,但是重排序后多线程会有问题
+
+#### as-if-serial规则和happens-before规则的区别
+
+* as-if-serial语义保证单线程内程序的执行结果不被改变,happens-before关系保证正确同步的多线程程序的执行结果不被改变。
+
+* as-if-serial语义给编写单线程程序的程序员创造了一个幻境:单线程程序是按程序的顺序来执行的。happens-before关系给编写正确同步的多线程程序的程序员创造了一个幻境:正确同步的多线程程序是按happens-before指定的顺序来执行的。
+
+* as-if-serial语义和happens-before这么做的目的,都是为了在不改变程序执行结果的前提下,尽可能地提高程序执行的并行度。
+
+#### 并发关键字 synchronized ?
+
+* 在 Java 中,synchronized 关键字是用来控制线程同步的,就是在多线程的环境下,控制 synchronized 代码段不被多个线程同时执行。synchronized 可以修饰类、方法、变量。
+
+* 另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
+
+#### 说说自己是怎么使用 synchronized 关键字,在项目中用到了吗
+
+**synchronized关键字最主要的三种使用方式:**
+
+* 修饰实例方法: 作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
+* 修饰静态方法: 也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。
+* 修饰代码块: 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
+
+`总结: synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。synchronized 关键字加到实例方法上是给对象实例上锁。尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓存功能!`
+
+#### 单例模式了解吗?给我解释一下双重检验锁方式实现单例模式!”
+
+**双重校验锁实现对象单例(线程安全)**
+
+**说明:**
+
+* 双锁机制的出现是为了解决前面同步问题和性能问题,看下面的代码,简单分析下确实是解决了多线程并行进来不会出现重复new对象,而且也实现了懒加载
+
+```java
+ public class Singleton {
+ private volatile static Singleton uniqueInstance;
+ private Singleton() {}
+
+ public static Singleton getUniqueInstance() {
+ //先判断对象是否已经实例过,没有实例化过才进入加锁代码
+ if (uniqueInstance == null) {
+ //类对象加锁
+ synchronized (Singleton.class) {
+ if (uniqueInstance == null) {
+ uniqueInstance = new Singleton();
+ }
+ }
+ }
+ return uniqueInstance;
+ }
+ }
+```
+`另外,需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。`
+
+* uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行:
+
+1. 为 uniqueInstance 分配内存空间
+2. 初始化 uniqueInstance
+3. 将 uniqueInstance 指向分配的内存地址
+
+`但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。`
+
+`使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。`
+
+#### 说一下 synchronized 底层实现原理?
+
+* Synchronized的语义底层是通过一个monitor(监视器锁)的对象来完成,
+
+* 每个对象有一个监视器锁(monitor)。每个Synchronized修饰过的代码当它的monitor被占用时就会处于锁定状态并且尝试获取monitor的所有权 ,过程:
+
+ 1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
+
+ 2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
+
+ 3、如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
+
+`synchronized是可以通过 反汇编指令 javap命令,查看相应的字节码文件。`
+
+#### synchronized可重入的原理
+
+* 重入锁是指一个线程获取到该锁之后,该线程可以继续获得该锁。底层原理维护一个计数器,当线程获取该锁时,计数器加一,再次获得该锁时继续加一,释放锁时,计数器减一,当计数器值为0时,表明该锁未被任何线程所持有,其它线程可以竞争获取锁。
+
+#### 什么是自旋
+
+* 很多 synchronized 里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。既然 synchronized 里面的代码执行得非常快,不妨让等待锁的线程不要被阻塞,而是在 synchronized 的边界做忙循环,这就是自旋。如果做了多次循环发现还没有获得锁,再阻塞,这样可能是一种更好的策略。
+* 忙循环:就是程序员用循环让一个线程等待,不像传统方法wait(), sleep() 或 yield() 它们都放弃了CPU控制,而忙循环不会放弃CPU,它就是在运行一个空循环。这么做的目的是为了保留CPU缓存,在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存。为了避免重建缓存和减少等待重建的时间就可以使用它了。
+
+#### 多线程中 synchronized 锁升级的原理是什么?
+
+* synchronized 锁升级原理:在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。
+
+`锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized 的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。`
+
+* 偏向锁,顾名思义,它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,减少加锁/解锁的一些CAS操作(比如等待队列的一些CAS操作),这种情况下,就会给线程加一个偏向锁。 如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁恢复到标准的轻量级锁。
+
+* 轻量级锁是由偏向所升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,轻量级锁就会升级为重量级锁;
+
+* 重量级锁是synchronized ,是 Java 虚拟机中最为基础的锁实现。在这种状态下,Java 虚拟机会阻塞加锁失败的线程,并且在目标锁被释放的时候,唤醒这些线程。
+
+#### 线程 B 怎么知道线程 A 修改了变量
+
+* (1)volatile 修饰变量
+
+* (2)synchronized 修饰修改变量的方法
+
+* (3)wait/notify
+
+* (4)while 轮询
+
+#### 当一个线程进入一个对象的 synchronized 方法 A 之后,其它线程是否可进入此对象的 synchronized 方法 B?
+
+* 不能。其它线程只能访问该对象的非同步方法,同步方法则不能进入。因为非静态方法上的 synchronized 修饰符要求执行方法时要获得对象的锁,如果已经进入A 方法说明对象锁已经被取走,那么试图进入 B 方法的线程就只能在等锁池(注意不是等待池哦)中等待对象的锁。
+
+#### synchronized、volatile、CAS 比较
+
+* (1)synchronized 是悲观锁,属于抢占式,会引起其他线程阻塞。
+
+* (2)volatile 提供多线程共享变量可见性和禁止指令重排序优化。
+
+* (3)CAS 是基于冲突检测的乐观锁(非阻塞)
+
+#### synchronized 和 Lock 有什么区别?
+
+* 首先synchronized是Java内置关键字,在JVM层面,Lock是个Java类;
+* synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
+* synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。
+* 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
+
+#### synchronized 和 ReentrantLock 区别是什么?
+
+* synchronized 是和 if、else、for、while 一样的关键字,ReentrantLock 是类,这是二者的本质区别。既然 ReentrantLock 是类,那么它就提供了比synchronized 更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量
+
+* synchronized 早期的实现比较低效,对比 ReentrantLock,大多数场景性能都相差较大,但是在 Java 6 中对 synchronized 进行了非常多的改进。
+
+* 相同点:两者都是可重入锁
+
+ 两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。
+
+* 主要区别如下:
+
+ * ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作;
+ * ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;
+ * ReentrantLock 只适用于代码块锁,而 synchronized 可以修饰类、方法、变量等。
+ * 二者的锁机制其实也是不一样的。ReentrantLock 底层调用的是 Unsafe 的park 方法加锁,synchronized 操作的应该是对象头中 mark word
+* Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
+
+ * 普通同步方法,锁是当前实例对象
+ * 静态同步方法,锁是当前类的class对象
+ * 同步方法块,锁是括号里面的对象
+
+#### volatile 关键字的作用
+
+* 对于可见性,Java 提供了 volatile 关键字来保证可见性和禁止指令重排。 volatile 提供 happens-before 的保证,确保一个线程的修改能对其他线程是可见的。当一个共享变量被 volatile 修饰时,它会保证修改的值会立即被更新到主内存中,当有其他线程需要读取时,它会去内存中读取新值。
+
+* 从实践角度而言,volatile 的一个重要作用就是和 CAS 结合,保证了原子性,详细的可以参见 java.util.concurrent.atomic 包下的类,比如 AtomicInteger。
+
+* volatile 常用于多线程环境下的单次操作(单次读或者单次写)。
+
+#### Java 中能创建 volatile 数组吗?
+
+* 能,Java 中可以创建 volatile 类型数组,不过只是一个指向数组的引用,而不是整个数组。意思是,如果改变引用指向的数组,将会受到 volatile 的保护,但是如果多个线程同时改变数组的元素,volatile 标示符就不能起到之前的保护作用了。
+
+#### volatile 变量和 atomic 变量有什么不同?
+
+* volatile 变量可以确保先行关系,即写操作会发生在后续的读操作之前, 但它并不能保证原子性。例如用 volatile 修饰 count 变量,那么 count++ 操作就不是原子性的。
+
+* 而 AtomicInteger 类提供的 atomic 方法可以让这种操作具有原子性如getAndIncrement()方法会原子性的进行增量操作把当前值加一,其它数据类型和引用变量也可以进行相似操作。
+
+#### volatile 能使得一个非原子操作变成原子操作吗?
+
+* 关键字volatile的主要作用是使变量在多个线程间可见,但无法保证原子性,对于多个线程访问同一个实例变量需要加锁进行同步。
+
+* 虽然volatile只能保证可见性不能保证原子性,但用volatile修饰long和double可以保证其操作原子性。
+
+**所以从Oracle Java Spec里面可以看到:**
+
+* 对于64位的long和double,如果没有被volatile修饰,那么对其操作可以不是原子的。在操作的时候,可以分成两步,每次对32位操作。
+* 如果使用volatile修饰long和double,那么其读写都是原子操作
+* 对于64位的引用地址的读写,都是原子操作
+* 在实现JVM时,可以自由选择是否把读写long和double作为原子操作
+* 推荐JVM实现为原子操作
+
+#### synchronized 和 volatile 的区别是什么?
+
+* synchronized 表示只有一个线程可以获取作用对象的锁,执行代码,阻塞其他线程。
+
+* volatile 表示变量在 CPU 的寄存器中是不确定的,必须从主存中读取。保证多线程环境下变量的可见性;禁止指令重排序。
+
+**区别**
+
+* volatile 是变量修饰符;synchronized 可以修饰类、方法、变量。
+
+* volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。
+
+* volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
+
+* volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
+
+* volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。但是volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,实际开发中使用 synchronized 关键字的场景还是更多一些。
+
+#### final不可变对象,它对写并发应用有什么帮助?
+
+* 不可变对象(Immutable Objects)即对象一旦被创建它的状态(对象的数据,也即对象属性值)就不能改变,反之即为可变对象(Mutable Objects)。
+
+* 不可变对象的类即为不可变类(Immutable Class)。Java 平台类库中包含许多不可变类,如 String、基本类型的包装类、BigInteger 和 BigDecimal 等。
+
+* 只有满足如下状态,一个对象才是不可变的;
+
+ * 它的状态不能在创建后再被修改;
+
+ * 所有域都是 final 类型;并且,它被正确创建(创建期间没有发生 this 引用的逸出)。
+
+`不可变对象保证了对象的内存可见性,对不可变对象的读取不需要进行额外的同步手段,提升了代码执行效率。`
+
+#### Lock 接口和synchronized 对比同步它有什么优势?
+
+* Lock 接口比同步方法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。
+
+* 它的优势有:
+
+ * (1)可以使锁更公平
+
+ * (2)可以使线程在等待锁的时候响应中断
+
+ * (3)可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间
+
+ * (4)可以在不同的范围,以不同的顺序获取和释放锁
+
+* 整体上来说 Lock 是 synchronized 的扩展版,Lock 提供了无条件的、可轮询的(tryLock 方法)、定时的(tryLock 带参方法)、可中断的(lockInterruptibly)、可多条件队列的(newCondition 方法)锁操作。另外 Lock 的实现类基本都支持非公平锁(默认)和公平锁,synchronized 只支持非公平锁,当然,在大部分情况下,非公平锁是高效的选择。
+
+#### 乐观锁和悲观锁的理解及如何实现,有哪些实现方式?
+
+* 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再比如 Java 里面的同步原语 synchronized 关键字的实现也是悲观锁。
+
+* 乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于 write_condition 机制,其实都是提供的乐观锁。在 Java中 java.util.concurrent.atomic 包下面的原子变量类就是使用了乐观锁的一种实现方式 CAS 实现的。
+
+#### 什么是 CAS
+
+* CAS 是 compare and swap 的缩写,即我们所说的比较交换。
+
+* cas 是一种基于锁的操作,而且是乐观锁。在 java 中锁分为乐观锁和悲观锁。悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加 version 来获取数据,性能较悲观锁有很大的提高。
+
+* CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和 A 的值是一样的,那么就将内存里面的值更新成 B。CAS是通过无限循环来获取数据的,若果在第一轮循环中,a 线程获取地址里面的值被b 线程修改了,那么 a 线程需要自旋,到下次循环才有可能机会执行。
+
+`java.util.concurrent.atomic 包下的类大多是使用 CAS 操作来实现的(AtomicInteger,AtomicBoolean,AtomicLong)`
+
+#### CAS 的会产生什么问题?
+
+* 1、ABA 问题:
+
+ 比如说一个线程 one 从内存位置 V 中取出 A,这时候另一个线程 two 也从内存中取出 A,并且 two 进行了一些操作变成了 B,然后 two 又将 V 位置的数据变成 A,这时候线程 one 进行 CAS 操作发现内存中仍然是 A,然后 one 操作成功。尽管线程 one 的 CAS 操作成功,但可能存在潜藏的问题。从 Java1.5 开始 JDK 的 atomic包里提供了一个类 AtomicStampedReference 来解决 ABA 问题。
+
+* 2、循环时间长开销大:
+
+ 对于资源竞争严重(线程冲突严重)的情况,CAS 自旋的概率会比较大,从而浪费更多的 CPU 资源,效率低于 synchronized。
+
+* 3、只能保证一个共享变量的原子操作:
+
+ 当对一个共享变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候就可以用锁。
+
+#### 什么是原子类
+
+* java.util.concurrent.atomic包:是原子类的小工具包,支持在单个变量上解除锁的线程安全编程 原子变量类相当于一种泛化的 volatile 变量,能够支持原子的和有条件的读-改-写操作。
+
+* 比如:AtomicInteger 表示一个int类型的值,并提供了 get 和 set 方法,这些 Volatile 类型的int变量在读取和写入上有着相同的内存语义。它还提供了一个原子的 compareAndSet 方法(如果该方法成功执行,那么将实现与读取/写入一个 volatile 变量相同的内存效果),以及原子的添加、递增和递减等方法。AtomicInteger 表面上非常像一个扩展的 Counter 类,但在发生竞争的情况下能提供更高的可伸缩性,因为它直接利用了硬件对并发的支持。
+
+`简单来说就是原子类来实现CAS无锁模式的算法`
+
+#### 原子类的常用类
+
+* AtomicBoolean
+* AtomicInteger
+* AtomicLong
+* AtomicReference
+
+#### 说一下 Atomic的原理?
+
+* Atomic包中的类基本的特性就是在多线程环境下,当有多个线程同时对单个(包括基本类型及引用类型)变量进行操作时,具有排他性,即当多个线程同时对该变量的值进行更新时,仅有一个线程能成功,而未成功的线程可以向自旋锁一样,继续尝试,一直等到执行成功。
+
+#### 死锁与活锁的区别,死锁与饥饿的区别?
+
+* 死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
+
+* 活锁:任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。
+
+* 活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,这就是所谓的“活”, 而处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。
+
+* 饥饿:一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行的状态。
+
+ Java 中导致饥饿的原因:
+
+ * 1、高优先级线程吞噬所有的低优先级线程的 CPU 时间。
+
+ * 2、线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前持续地对该同步块进行访问。
+
+ * 3、线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的 wait 方法),因为其他线程总是被持续地获得唤醒。
+
+## 线程池
+
+#### 什么是线程池?
+
+* Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池。在开发过程中,合理地使用线程池能够带来许多好处。
+ * 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
+ * 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
+ * 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用
+
+#### 线程池作用?
+
+* 线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时间,从而提高效率。
+
+* 如果一个线程所需要执行的时间非常长的话,就没必要用线程池了(不是不能作长时间操作,而是不宜。本来降低线程创建和销毁,结果你那么久我还不好控制还不如直接创建线程),况且我们还不能控制线程池中线程的开始、挂起、和中止。
+
+#### 线程池有什么优点?
+
+* 降低资源消耗:重用存在的线程,减少对象创建销毁的开销。
+
+* 提高响应速度。可有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
+
+* 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
+
+* 附加功能:提供定时执行、定期执行、单线程、并发数控制等功能。
+
+#### 什么是ThreadPoolExecutor?
+
+* **ThreadPoolExecutor就是线程池**
+
+ ThreadPoolExecutor其实也是JAVA的一个类,我们一般通过Executors工厂类的方法,通过传入不同的参数,就可以构造出适用于不同应用场景下的ThreadPoolExecutor(线程池)
+
+构造参数图:
+
+`构造参数参数介绍:`corePoolSize 核心线程数量
+maximumPoolSize 最大线程数量
+keepAliveTime 线程保持时间,N个时间单位
+unit 时间单位(比如秒,分)
+workQueue 阻塞队列
+threadFactory 线程工厂
+handler 线程池拒绝策略
+复制代码
+#### 什么是Executors?
+
+* **Executors框架实现的就是线程池的功能。**
+
+ Executors工厂类中提供的newCachedThreadPool、newFixedThreadPool 、newScheduledThreadPool 、newSingleThreadExecutor 等方法其实也只是ThreadPoolExecutor的构造函数参数不同而已。通过传入不同的参数,就可以构造出适用于不同应用场景下的线程池,
+
+Executor工厂类如何创建线程池图:
+
+
+#### 线程池四种创建方式?
+
+* **Java通过Executors(jdk1.5并发包)提供四种线程池,分别为:**
+ 1. newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
+ 2. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
+ 3. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
+ 4. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
+
+#### 在 Java 中 Executor 和 Executors 的区别?
+
+* Executors 工具类的不同方法按照我们的需求创建了不同的线程池,来满足业务的需求。
+
+* Executor 接口对象能执行我们的线程任务。
+
+* ExecutorService 接口继承了 Executor 接口并进行了扩展,提供了更多的方法我们能获得任务执行的状态并且可以获取任务的返回值。
+
+* 使用 ThreadPoolExecutor 可以创建自定义线程池。
+
+#### 四种构建线程池的区别及特点?
+
+##### 1\. newCachedThreadPool
+
+* **特点**:newCachedThreadPool创建一个可缓存线程池,如果当前线程池的长度超过了处理的需要时,它可以灵活的回收空闲的线程,当需要增加时, 它可以灵活的添加新的线程,而不会对池的长度作任何限制
+
+* **缺点**:他虽然可以无线的新建线程,但是容易造成堆外内存溢出,因为它的最大值是在初始化的时候设置为 Integer.MAX_VALUE,一般来说机器都没那么大内存给它不断使用。当然知道可能出问题的点,就可以去重写一个方法限制一下这个最大值
+
+* **总结**:线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
+
+* **代码示例:**
+
+```java
+package com.lijie;
+
+ import java.util.concurrent.ExecutorService;
+ import java.util.concurrent.Executors;
+
+ public class TestNewCachedThreadPool {
+ public static void main(String[] args) {
+ // 创建无限大小线程池,由jvm自动回收
+ ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
+ for (int i = 0; i < 10; i++) {
+ final int temp = i;
+ newCachedThreadPool.execute(new Runnable() {
+ public void run() {
+ try {
+ Thread.sleep(100);
+ } catch (Exception e) {
+ }
+ System.out.println(Thread.currentThread().getName() + ",i==" + temp);
+ }
+ });
+ }
+ }
+ }
+
+```
+
+##### 2.newFixedThreadPool
+
+* **特点**:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。定长线程池的大小最好根据系统资源进行设置。
+
+* **缺点**:线程数量是固定的,但是阻塞队列是无界队列。如果有很多请求积压,阻塞队列越来越长,容易导致OOM(超出内存空间)
+
+* **总结**:请求的挤压一定要和分配的线程池大小匹配,定线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()
+
+`Runtime.getRuntime().availableProcessors()方法是查看电脑CPU核心数量)`
+
+* **代码示例:**
+
+```java
+package com.lijie;
+
+ import java.util.concurrent.ExecutorService;
+ import java.util.concurrent.Executors;
+
+ public class TestNewFixedThreadPool {
+ public static void main(String[] args) {
+ ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3);
+ for (int i = 0; i < 10; i++) {
+ final int temp = i;
+ newFixedThreadPool.execute(new Runnable() {
+ public void run() {
+ System.out.println(Thread.currentThread().getName() + ",i==" + temp);
+ }
+ });
+ }
+ }
+ }
+
+```
+
+##### 3.newScheduledThreadPool
+
+* **特点**:创建一个固定长度的线程池,而且支持定时的以及周期性的任务执行,类似于Timer(Timer是Java的一个定时器类)
+
+* **缺点**:由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务(比如:一个任务出错,以后的任务都无法继续)。
+
+* **代码示例:**
+
+```java
+package com.lijie;
+
+ import java.util.concurrent.Executors;
+ import java.util.concurrent.ScheduledExecutorService;
+ import java.util.concurrent.TimeUnit;
+
+ public class TestNewScheduledThreadPool {
+ public static void main(String[] args) {
+ //定义线程池大小为3
+ ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(3);
+ for (int i = 0; i < 10; i++) {
+ final int temp = i;
+ newScheduledThreadPool.schedule(new Runnable() {
+ public void run() {
+ System.out.println("i:" + temp);
+ }
+ }, 3, TimeUnit.SECONDS);//这里表示延迟3秒执行。
+ }
+ }
+ }
+
+```
+
+##### 4.newSingleThreadExecutor
+
+* **特点**:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它,他必须保证前一项任务执行完毕才能执行后一项。保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
+
+* **缺点**:缺点的话,很明显,他是单线程的,高并发业务下有点无力
+
+* **总结**:保证所有任务按照指定顺序执行的,如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它
+
+* **代码示例:**
+
+```java
+package com.lijie;
+
+ import java.util.concurrent.ExecutorService;
+ import java.util.concurrent.Executors;
+
+ public class TestNewSingleThreadExecutor {
+ public static void main(String[] args) {
+ ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
+ for (int i = 0; i < 10; i++) {
+ final int index = i;
+ newSingleThreadExecutor.execute(new Runnable() {
+ public void run() {
+ System.out.println(Thread.currentThread().getName() + " index:" + index);
+ try {
+ Thread.sleep(200);
+ } catch (Exception e) {
+ }
+ }
+ });
+ }
+ }
+ }
+
+```
+
+#### 线程池都有哪些状态?
+
+* RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。
+* SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。
+* STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。
+* TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。
+* TERMINATED:terminated()方法结束后,线程池的状态就会变成这个。
+
+#### 线程池中 submit() 和 execute() 方法有什么区别?
+
+* 相同点:
+ * 相同点就是都可以开启线程执行池中的任务。
+* 不同点:
+ * 接收参数:execute()只能执行 Runnable 类型的任务。submit()可以执行 Runnable 和 Callable 类型的任务。
+ * 返回值:submit()方法可以返回持有计算结果的 Future 对象,而execute()没有
+ * 异常处理:submit()方便Exception处理
+
+#### 什么是线程组,为什么在 Java 中不推荐使用?
+
+* ThreadGroup 类,可以把线程归属到某一个线程组中,线程组中可以有线程对象,也可以有线程组,组中还可以有线程,这样的组织结构有点类似于树的形式。
+
+* 线程组和线程池是两个不同的概念,他们的作用完全不同,前者是为了方便线程的管理,后者是为了管理线程的生命周期,复用线程,减少创建销毁线程的开销。
+
+* 为什么不推荐使用线程组?因为使用有很多的安全隐患吧,没有具体追究,如果需要使用,推荐使用线程池。
+
+#### ThreadPoolExecutor饱和策略有哪些?
+
+`如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任时,ThreadPoolTaskExecutor 定义一些策略:`
+
+* ThreadPoolExecutor.AbortPolicy:抛出 RejectedExecutionException来拒绝新任务的处理。
+* ThreadPoolExecutor.CallerRunsPolicy:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。
+* ThreadPoolExecutor.DiscardPolicy:不处理新任务,直接丢弃掉。
+* ThreadPoolExecutor.DiscardOldestPolicy: 此策略将丢弃最早的未处理的任务请求。
+
+#### 如何自定义线程线程池?
+
+* 先看ThreadPoolExecutor(线程池)这个类的构造参数
+
+ 构造参数参数介绍:
+```java
+ corePoolSize 核心线程数量
+ maximumPoolSize 最大线程数量
+ keepAliveTime 线程保持时间,N个时间单位
+ unit 时间单位(比如秒,分)
+ workQueue 阻塞队列
+ threadFactory 线程工厂
+ handler 线程池拒绝策略
+
+```
+* 代码示例:
+
+```java
+package com.lijie;
+
+ import java.util.concurrent.ArrayBlockingQueue;
+ import java.util.concurrent.ThreadPoolExecutor;
+ import java.util.concurrent.TimeUnit;
+
+ public class Test001 {
+ public static void main(String[] args) {
+ //创建线程池
+ ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3));
+ for (int i = 1; i <= 6; i++) {
+ TaskThred t1 = new TaskThred("任务" + i);
+ //executor.execute(t1);是执行线程方法
+ executor.execute(t1);
+ }
+ //executor.shutdown()不再接受新的任务,并且等待之前提交的任务都执行完再关闭,阻塞队列中的任务不会再执行。
+ executor.shutdown();
+ }
+ }
+
+ class TaskThred implements Runnable {
+ private String taskName;
+
+ public TaskThred(String taskName) {
+ this.taskName = taskName;
+ }
+ public void run() {
+ System.out.println(Thread.currentThread().getName() + taskName);
+ }
+ }
+
+```
+
+#### 线程池的执行原理?
+
+
+
+* 提交一个任务到线程池中,线程池的处理流程如下:
+
+ 1. 判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。
+
+ 2. 线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
+
+ 3. 判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
+
+#### 如何合理分配线程池大小?
+
+* 要合理的分配线程池的大小要根据实际情况来定,简单的来说的话就是根据CPU密集和IO密集来分配
+
+##### 什么是CPU密集
+
+* CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。
+
+* CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程),而在单核CPU上,无论你开几个模拟的多线程,该任务都不可能得到加速,因为CPU总的运算能力就那样。
+
+##### 什么是IO密集
+
+* IO密集型,即该任务需要大量的IO,即大量的阻塞。在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待。所以在IO密集型任务中使用多线程可以大大的加速程序运行,即时在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。
+
+##### 分配CPU和IO密集:
+
+1. CPU密集型时,任务可以少配置线程数,大概和机器的cpu核数相当,这样可以使得每个线程都在执行任务
+
+2. IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数
+
+##### 精确来说的话的话:
+
+* 从以下几个角度分析任务的特性:
+
+ * 任务的性质:CPU密集型任务、IO密集型任务、混合型任务。
+
+ * 任务的优先级:高、中、低。
+
+ * 任务的执行时间:长、中、短。
+
+ * 任务的依赖性:是否依赖其他系统资源,如数据库连接等。
+
+**可以得出一个结论:**
+
+* 线程等待时间比CPU执行时间比例越高,需要越多线程。
+* 线程CPU执行时间比等待时间比例越高,需要越少线程。
+
+## 并发容器
+
+#### 你经常使用什么并发容器,为什么?
+
+* 答:Vector、ConcurrentHashMap、HasTable
+
+* 一般软件开发中容器用的最多的就是HashMap、ArrayList,LinkedList ,等等
+
+* 但是在多线程开发中就不能乱用容器,如果使用了未加锁(非同步)的的集合,你的数据就会非常的混乱。由此在多线程开发中需要使用的容器必须是加锁(同步)的容器。
+
+#### 什么是Vector
+
+* Vector与ArrayList一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,访问它比访问ArrayList慢很多
+
+ (`ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。当从ArrayList的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。ArrayList的缺点是每个元素之间不能有间隔。`)
+
+#### ArrayList和Vector有什么不同之处?
+
+* Vector方法带上了synchronized关键字,是线程同步的
+
+1. ArrayList添加方法源码
+2. Vector添加源码(加锁了synchronized关键字)
+
+#### 为什么HashTable是线程安全的?
+
+* 因为HasTable的内部方法都被synchronized修饰了,所以是线程安全的。其他的都和HashMap一样
+
+1. HashMap添加方法的源码
+2. HashTable添加方法的源码
+
+#### 用过ConcurrentHashMap,讲一下他和HashTable的不同之处?
+
+* ConcurrentHashMap是Java5中支持高并发、高吞吐量的线程安全HashMap实现。它由Segment数组结构和HashEntry数组结构组成。Segment数组在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键-值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构;一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素;每个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。
+
+* 看不懂???很正常,我也看不懂
+
+* 总结:
+
+ 1. HashTable就是实现了HashMap加上了synchronized,而ConcurrentHashMap底层采用分段的数组+链表实现,线程安全
+ 2. ConcurrentHashMap通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。
+ 3. 并且读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。
+ 4. Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术
+ 5. 扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容
+
+#### Collections.synchronized * 是什么?
+
+`注意:* 号代表后面是还有内容的`
+
+* 此方法是干什么的呢,他完完全全的可以把List、Map、Set接口底下的集合变成线程安全的集合
+
+* Collections.synchronized * :原理是什么,我猜的话是代理模式:[Java代理模式理解](https://link.juejin.cn?target=https%3A%2F%2Fblog.csdn.net%2Fweixin_43122090%2Farticle%2Fdetails%2F104883274 "https://blog.csdn.net/weixin_43122090/article/details/104883274")
+
+
+#### Java 中 ConcurrentHashMap 的并发度是什么?
+
+* ConcurrentHashMap 把实际 map 划分成若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度获得的,它是 ConcurrentHashMap 类构造函数的一个可选参数,默认值为 16,这样在多线程情况下就能避免争用。
+
+* 在 JDK8 后,它摒弃了 Segment(锁段)的概念,而是启用了一种全新的方式实现,利用 CAS 算法。同时加入了更多的辅助变量来提高并发度,具体内容还是查看源码吧。
+
+#### 什么是并发容器的实现?
+
+* 何为同步容器:可以简单地理解为通过 synchronized 来实现同步的容器,如果有多个线程调用同步容器的方法,它们将会串行执行。比如 Vector,Hashtable,以及 Collections.synchronizedSet,synchronizedList 等方法返回的容器。可以通过查看 Vector,Hashtable 等这些同步容器的实现代码,可以看到这些容器实现线程安全的方式就是将它们的状态封装起来,并在需要同步的方法上加上关键字 synchronized。
+
+* 并发容器使用了与同步容器完全不同的加锁策略来提供更高的并发性和伸缩性,例如在 ConcurrentHashMap 中采用了一种粒度更细的加锁机制,可以称为分段锁,在这种锁机制下,允许任意数量的读线程并发地访问 map,并且执行读操作的线程和写操作的线程也可以并发的访问 map,同时允许一定数量的写操作线程并发地修改 map,所以它可以在并发环境下实现更高的吞吐量。
+
+#### Java 中的同步集合与并发集合有什么区别?
+
+* 同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发集合的可扩展性更高。在 Java1.5 之前程序员们只有同步集合来用且在多线程并发的时候会导致争用,阻碍了系统的扩展性。Java5 介绍了并发集合像ConcurrentHashMap,不仅提供线程安全还用锁分离和内部分区等现代技术提高了可扩展性。
+
+#### SynchronizedMap 和 ConcurrentHashMap 有什么区别?
+
+* SynchronizedMap 一次锁住整张表来保证线程安全,所以每次只能有一个线程来访为 map。
+
+* ConcurrentHashMap 使用分段锁来保证在多线程下的性能。
+
+* ConcurrentHashMap 中则是一次锁住一个桶。ConcurrentHashMap 默认将hash 表分为 16 个桶,诸如 get,put,remove 等常用操作只锁当前需要用到的桶。
+
+* 这样,原来只能一个线程进入,现在却能同时有 16 个写线程执行,并发性能的提升是显而易见的。
+
+* 另外 ConcurrentHashMap 使用了一种不同的迭代方式。在这种迭代方式中,当iterator 被创建后集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是在改变时 new 新的数据从而不影响原有的数据,iterator 完成后再将头指针替换为新的数据 ,这样 iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变。
+
+#### CopyOnWriteArrayList 是什么?
+
+* CopyOnWriteArrayList 是一个并发容器。有很多人称它是线程安全的,我认为这句话不严谨,缺少一个前提条件,那就是非复合场景下操作它是线程安全的。
+
+* CopyOnWriteArrayList(免锁容器)的好处之一是当多个迭代器同时遍历和修改这个列表时,不会抛出 ConcurrentModificationException。在CopyOnWriteArrayList 中,写入将导致创建整个底层数组的副本,而源数组将保留在原地,使得复制的数组在被修改时,读取操作可以安全地执行。
+
+#### CopyOnWriteArrayList 的使用场景?
+
+* 合适读多写少的场景。
+
+#### CopyOnWriteArrayList 的缺点?
+
+* 由于写操作的时候,需要拷贝数组,会消耗内存,如果原数组的内容比较多的情况下,可能导致 young gc 或者 full gc。
+* 不能用于实时读的场景,像拷贝数组、新增元素都需要时间,所以调用一个 set 操作后,读取到数据可能还是旧的,虽然CopyOnWriteArrayList 能做到最终一致性,但是还是没法满足实时性要求。
+* 由于实际使用中可能没法保证 CopyOnWriteArrayList 到底要放置多少数据,万一数据稍微有点多,每次 add/set 都要重新复制数组,这个代价实在太高昂了。在高性能的互联网应用中,这种操作分分钟引起故障。
+
+#### CopyOnWriteArrayList 的设计思想?
+
+* 读写分离,读和写分开
+* 最终一致性
+* 使用另外开辟空间的思路,来解决并发冲突
+
+## 并发队列
+
+#### 什么是并发队列:
+
+* 消息队列很多人知道:消息队列是分布式系统中重要的组件,是系统与系统直接的通信
+
+* 并发队列是什么:并发队列多个线程以有次序共享数据的重要组件
+
+#### 并发队列和并发集合的区别:
+
+`那就有可能要说了,我们并发集合不是也可以实现多线程之间的数据共享吗,其实也是有区别的:`
+
+* 队列遵循“先进先出”的规则,可以想象成排队检票,队列一般用来解决大数据量采集处理和显示的。
+
+* 并发集合就是在多个线程中共享数据的
+
+#### 怎么判断并发队列是阻塞队列还是非阻塞队列
+
+* 在并发队列上JDK提供了Queue接口,一个是以Queue接口下的BlockingQueue接口为代表的阻塞队列,另一个是高性能(无堵塞)队列。
+
+#### 阻塞队列和非阻塞队列区别
+
+* 当队列阻塞队列为空的时,从队列中获取元素的操作将会被阻塞。
+
+* 或者当阻塞队列是满时,往队列里添加元素的操作会被阻塞。
+
+* 或者试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。
+
+* 试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程使队列重新变得空闲起来
+
+#### 常用并发列队的介绍:
+
+1. **非堵塞队列:**
+
+ 1. **ArrayDeque, (数组双端队列)**
+
+ ArrayDeque (非堵塞队列)是JDK容器中的一个双端队列实现,内部使用数组进行元素存储,不允许存储null值,可以高效的进行元素查找和尾部插入取出,是用作队列、双端队列、栈的绝佳选择,性能比LinkedList还要好。
+
+ 2. **PriorityQueue, (优先级队列)**
+
+ PriorityQueue (非堵塞队列) 一个基于优先级的无界优先级队列。优先级队列的元素按照其自然顺序进行排序,或者根据构造队列时提供的 Comparator 进行排序,具体取决于所使用的构造方法。该队列不允许使用 null 元素也不允许插入不可比较的对象
+
+ 3. **ConcurrentLinkedQueue, (基于链表的并发队列)**
+
+ ConcurrentLinkedQueue (非堵塞队列): 是一个适用于高并发场景下的队列,通过无锁的方式,实现了高并发状态下的高性能。ConcurrentLinkedQueue的性能要好于BlockingQueue接口,它是一个基于链接节点的无界线程安全队列。该队列的元素遵循先进先出的原则。该队列不允许null元素。
+
+2. **堵塞队列:**
+
+ 1. **DelayQueue, (基于时间优先级的队列,延期阻塞队列)**
+
+ DelayQueue是一个没有边界BlockingQueue实现,加入其中的元素必需实现Delayed接口。当生产者线程调用put之类的方法加入元素时,会触发Delayed接口中的compareTo方法进行排序,也就是说队列中元素的顺序是按到期时间排序的,而非它们进入队列的顺序。排在队列头部的元素是最早到期的,越往后到期时间赿晚。
+
+ 2. **ArrayBlockingQueue, (基于数组的并发阻塞队列)**
+
+ ArrayBlockingQueue是一个有边界的阻塞队列,它的内部实现是一个数组。有边界的意思是它的容量是有限的,我们必须在其初始化的时候指定它的容量大小,容量大小一旦指定就不可改变。ArrayBlockingQueue是以先进先出的方式存储数据
+
+ 3. **LinkedBlockingQueue, (基于链表的FIFO阻塞队列)**
+
+ LinkedBlockingQueue阻塞队列大小的配置是可选的,如果我们初始化时指定一个大小,它就是有边界的,如果不指定,它就是无边界的。说是无边界,其实是采用了默认大小为Integer.MAX_VALUE的容量 。它的内部实现是一个链表。
+
+ 4. **LinkedBlockingDeque, (基于链表的FIFO双端阻塞队列)**
+
+ LinkedBlockingDeque是一个由链表结构组成的双向阻塞队列,即可以从队列的两端插入和移除元素。双向队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争。
+
+ 相比于其他阻塞队列,LinkedBlockingDeque多了addFirst、addLast、peekFirst、peekLast等方法,以first结尾的方法,表示插入、获取获移除双端队列的第一个元素。以last结尾的方法,表示插入、获取获移除双端队列的最后一个元素。
+
+ LinkedBlockingDeque是可选容量的,在初始化时可以设置容量防止其过度膨胀,如果不设置,默认容量大小为Integer.MAX_VALUE。
+
+ 5. **PriorityBlockingQueue, (带优先级的无界阻塞队列)**
+
+ priorityBlockingQueue是一个无界队列,它没有限制,在内存允许的情况下可以无限添加元素;它又是具有优先级的队列,是通过构造函数传入的对象来判断,传入的对象必须实现comparable接口。
+
+ 6. **SynchronousQueue (并发同步阻塞队列)**
+
+ SynchronousQueue是一个内部只能包含一个元素的队列。插入元素到队列的线程被阻塞,直到另一个线程从队列中获取了队列中存储的元素。同样,如果线程尝试获取元素并且当前不存在任何元素,则该线程将被阻塞,直到线程将元素插入队列。
+
+ 将这个类称为队列有点夸大其词。这更像是一个点。
+
+#### 并发队列的常用方法
+
+`不管是那种列队,是那个类,当是他们使用的方法都是差不多的`
+
+| 方法名 | 描述 |
+| --- | --- |
+| add() | 在不超出队列长度的情况下插入元素,可以立即执行,成功返回true,如果队列满了就抛出异常。 |
+| offer() | 在不超出队列长度的情况下插入元素的时候则可以立即在队列的尾部插入指定元素,成功时返回true,如果此队列已满,则返回false。 |
+| put() | 插入元素的时候,如果队列满了就进行等待,直到队列可用。 |
+| take() | 从队列中获取值,如果队列中没有值,线程会一直阻塞,直到队列中有值,并且该方法取得了该值。 |
+| poll(long timeout, TimeUnit unit) | 在给定的时间里,从队列中获取值,如果没有取到会抛出异常。 |
+| remainingCapacity() | 获取队列中剩余的空间。 |
+| remove(Object o) | 从队列中移除指定的值。 |
+| contains(Object o) | 判断队列中是否拥有该值。 |
+| drainTo(Collection c) | 将队列中值,全部移除,并发设置到给定的集合中。 |
+
+## 并发工具类
+
+#### 常用的并发工具类有哪些?
+
+* CountDownLatch
+
+ CountDownLatch 类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他3个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。
+
+* CyclicBarrier (回环栅栏) CyclicBarrier它的作用就是会让所有线程都等待完成后才会继续下一步行动。
+
+ CyclicBarrier初始化时规定一个数目,然后计算调用了CyclicBarrier.await()进入等待的线程数。当线程数达到了这个数目时,所有进入等待状态的线程被唤醒并继续。
+
+ CyclicBarrier初始时还可带一个Runnable的参数, 此Runnable任务在CyclicBarrier的数目达到后,所有其它线程被唤醒前被执行。
+
+* Semaphore (信号量) Semaphore 是 synchronized 的加强版,作用是控制线程的并发数量(允许自定义多少线程同时访问)。就这一点而言,单纯的synchronized 关键字是实现不了的。
+
+ Semaphore是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。Semaphore可以用来构建一些对象池,资源池之类的,比如数据库连接池,我们也可以创建计数为1的Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量,表示两种互斥状态。它的用法如下:
+
+作者:小杰要吃蛋
+链接:https://juejin.cn/post/6844904125755293710
+来源:稀土掘金
+著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
\ No newline at end of file
diff --git "a/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\345\274\202\345\270\270.md" "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\345\274\202\345\270\270.md"
new file mode 100644
index 0000000..566eeaa
--- /dev/null
+++ "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\345\274\202\345\270\270.md"
@@ -0,0 +1,739 @@
+
+
+## Java异常架构与异常关键字
+
+### Java异常简介
+
+* Java异常是Java提供的一种识别及响应错误的一致性机制。
+ Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。在有效使用异常的情况下,异常能清晰的回答what, where, why这3个问题:异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪”抛出,异常信息回答了“为什么”会抛出。
+
+### Java异常架构
+
+
+#### 1\. Throwable
+
+* Throwable 是 Java 语言中所有错误与异常的超类。
+
+* Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示发生了异常情况。
+
+* Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。
+
+#### 2\. Error(错误)
+
+* **定义**:Error 类及其子类。程序中无法处理的错误,表示运行应用程序中出现了严重的错误。
+
+* **特点**:此类错误一般表示代码运行时 JVM 出现问题。通常有 Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如 OutOfMemoryError:内存不足错误;StackOverflowError:栈溢出错误。此类错误发生时,JVM 将终止线程。
+
+* 这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错误。按照Java惯例,我们是不应该实现任何新的Error子类的!
+
+#### 3\. Exception(异常)
+
+* 程序本身可以捕获并且可以处理的异常。Exception 这种异常又分为两类:运行时异常和编译时异常。
+
+##### 运行时异常
+
+* **定义**:RuntimeException 类及其子类,表示 JVM 在运行期间可能出现的异常。
+
+* **特点**:Java 编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。比如NullPointerException空指针异常、ArrayIndexOutBoundException数组下标越界异常、ClassCastException类型转换异常、ArithmeticExecption算术异常。此类异常属于不受检异常,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。虽然 Java 编译器不会检查运行时异常,但是我们也可以通过 throws 进行声明抛出,也可以通过 try-catch 对它进行捕获处理。如果产生运行时异常,则需要通过修改代码来进行避免。例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生!
+
+* RuntimeException 异常会由 Java 虚拟机自动抛出并自动捕获(**就算我们没写异常捕获语句运行时也会抛出错误**!!),此类异常的出现绝大数情况是代码本身有问题应该从逻辑上去解决并改进代码。
+
+##### 编译时异常
+
+* **定义**: Exception 中除 RuntimeException 及其子类之外的异常。
+
+* **特点**: Java 编译器会检查它。如果程序中出现此类异常,比如 ClassNotFoundException(没有找到指定的类异常),IOException(IO流异常),要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常类。**该异常我们必须手动在代码里添加捕获语句来处理该异常**。
+
+#### 4\. 受检异常与非受检异常
+
+* Java 的所有异常可以分为受检异常(checked exception)和非受检异常(unchecked exception)。
+
+##### 受检异常
+
+* 编译器要求必须处理的异常。正确的程序在运行过程中,经常容易出现的、符合预期的异常情况。一旦发生此类异常,就必须采用某种方式进行处理。**除 RuntimeException 及其子类外,其他的 Exception 异常都属于受检异常**。编译器会检查此类异常,也就是说当编译器检查到应用中的某处可能会此类异常时,将会提示你处理本异常——要么使用try-catch捕获,要么使用方法签名中用 throws 关键字抛出,否则编译不通过。
+
+##### 非受检异常
+
+* 编译器不会进行检查并且不要求必须处理的异常,也就说当程序中出现此类异常时,即使我们没有try-catch捕获它,也没有使用throws抛出该异常,编译也会正常通过。**该类异常包括运行时异常(RuntimeException极其子类)和错误(Error)。**
+
+### Java异常关键字
+
+* **try** – 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
+* **catch** – 用于捕获异常。catch用来捕获try语句块中发生的异常。
+* **finally** – finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
+* **throw** – 用于抛出异常。
+* **throws** – 用在方法签名中,用于声明该方法可能抛出的异常。
+
+## Java异常处理
+
+
+
+* Java 通过面向对象的方法进行异常处理,一旦方法抛出异常,系统自动根据该异常对象寻找合适异常处理器(Exception Handler)来处理该异常,把各种不同的异常进行分类,并提供了良好的接口。在 Java 中,每个异常都是一个对象,它是 Throwable 类或其子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并可以对其进行处理。Java 的异常处理是通过 5 个关键词来实现的:try、catch、throw、throws 和 finally。
+
+* 在Java应用中,异常的处理机制分为声明异常,抛出异常和捕获异常。
+
+### 声明异常
+
+* 通常,应该捕获那些知道如何处理的异常,将不知道如何处理的异常继续传递下去。传递异常可以在方法签名处使用 **throws** 关键字声明可能会抛出的异常。
+
+**注意**
+
+* 非检查异常(Error、RuntimeException 或它们的子类)不可使用 throws 关键字来声明要抛出的异常。
+* 一个方法出现编译时异常,就需要 try-catch/ throws 处理,否则会导致编译错误。
+
+### 抛出异常
+
+* 如果你觉得解决不了某些异常问题,且不需要调用者处理,那么你可以抛出异常。
+
+* throw关键字作用是在方法内部抛出一个`Throwable`类型的异常。任何Java代码都可以通过throw语句抛出异常。
+
+### 捕获异常
+
+* 程序通常在运行之前不报错,但是运行后可能会出现某些未知的错误,但是还不想直接抛出到上一级,那么就需要通过try…catch…的形式进行异常捕获,之后根据不同的异常情况来进行相应的处理。
+
+### 如何选择异常类型
+
+* 可以根据下图来选择是捕获异常,声明异常还是抛出异常
+
+
+### 常见异常处理方式
+
+#### 直接抛出异常
+
+* 通常,应该捕获那些知道如何处理的异常,将不知道如何处理的异常继续传递下去。传递异常可以在方法签名处使用 **throws** 关键字声明可能会抛出的异常。
+
+private static void readFile(String filePath) throws IOException {
+ File file = new File(filePath);
+ String result;
+ BufferedReader reader = new BufferedReader(new FileReader(file));
+ while((result = reader.readLine())!=null) {
+ System.out.println(result);
+ }
+ reader.close();
+}
+复制代码
+#### 封装异常再抛出
+
+* 有时我们会从 catch 中抛出一个异常,目的是为了改变异常的类型。多用于在多系统集成时,当某个子系统故障,异常类型可能有多种,可以用统一的异常类型向外暴露,不需暴露太多内部异常细节。
+
+private static void readFile(String filePath) throws MyException {
+ try {
+ // code
+ } catch (IOException e) {
+ MyException ex = new MyException("read file failed.");
+ ex.initCause(e);
+ throw ex;
+ }
+}
+复制代码
+#### 捕获异常
+
+* 在一个 try-catch 语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理
+
+private static void readFile(String filePath) {
+ try {
+ // code
+ } catch (FileNotFoundException e) {
+ // handle FileNotFoundException
+ } catch (IOException e){
+ // handle IOException
+ }
+}
+复制代码
+
+* 同一个 catch 也可以捕获多种类型异常,用 | 隔开
+
+private static void readFile(String filePath) {
+ try {
+ // code
+ } catch (FileNotFoundException | UnknownHostException e) {
+ // handle FileNotFoundException or UnknownHostException
+ } catch (IOException e){
+ // handle IOException
+ }
+}
+复制代码
+#### 自定义异常
+
+* 习惯上,定义一个异常类应包含两个构造函数,一个无参构造函数和一个带有详细描述信息的构造函数(Throwable 的 toString 方法会打印这些详细信息,调试时很有用)
+
+public class MyException extends Exception {
+ public MyException(){ }
+ public MyException(String msg){
+ super(msg);
+ }
+ // ...
+}
+复制代码
+#### try-catch-finally
+
+* 当方法中发生异常,异常处之后的代码不会再执行,如果之前获取了一些本地资源需要释放,则需要在方法正常结束时和 catch 语句中都调用释放本地资源的代码,显得代码比较繁琐,finally 语句可以解决这个问题。
+
+private static void readFile(String filePath) throws MyException {
+ File file = new File(filePath);
+ String result;
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new FileReader(file));
+ while((result = reader.readLine())!=null) {
+ System.out.println(result);
+ }
+ } catch (IOException e) {
+ System.out.println("readFile method catch block.");
+ MyException ex = new MyException("read file failed.");
+ ex.initCause(e);
+ throw ex;
+ } finally {
+ System.out.println("readFile method finally block.");
+ if (null != reader) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
+复制代码
+
+* 调用该方法时,读取文件时若发生异常,代码会进入 catch 代码块,之后进入 finally 代码块;若读取文件时未发生异常,则会跳过 catch 代码块直接进入 finally 代码块。所以无论代码中是否发生异常,fianlly 中的代码都会执行。
+
+* 若 catch 代码块中包含 return 语句,finally 中的代码还会执行吗?将以上代码中的 catch 子句修改如下:
+
+catch (IOException e) {
+ System.out.println("readFile method catch block.");
+ return;
+}
+复制代码
+
+* 调用 readFile 方法,观察当 catch 子句中调用 return 语句时,finally 子句是否执行
+
+readFile method catch block.
+readFile method finally block.
+复制代码
+
+* 可见,即使 catch 中包含了 return 语句,finally 子句依然会执行。若 finally 中也包含 return 语句,finally 中的 return 会覆盖前面的 return.
+
+#### try-with-resource
+
+* 上面例子中,finally 中的 close 方法也可能抛出 IOException, 从而覆盖了原始异常。JAVA 7 提供了更优雅的方式来实现资源的自动释放,自动释放的资源需要是实现了 AutoCloseable 接口的类。
+
+private static void tryWithResourceTest(){
+ try (Scanner scanner = new Scanner(new FileInputStream("c:/abc"),"UTF-8")){
+ // code
+ } catch (IOException e){
+ // handle exception
+ }
+}
+复制代码
+
+* try 代码块退出时,会自动调用 scanner.close 方法,和把 scanner.close 方法放在 finally 代码块中不同的是,若 scanner.close 抛出异常,则会被抑制,抛出的仍然为原始异常。被抑制的异常会由 addSusppressed 方法添加到原来的异常,如果想要获取被抑制的异常列表,可以调用 getSuppressed 方法来获取。
+
+## Java异常常见面试题
+
+### 1\. Error 和 Exception 区别是什么?
+
+* Error 类型的错误通常为虚拟机相关错误,如系统崩溃,内存不足,堆栈溢出等,编译器不会对这类错误进行检测,JAVA 应用程序也不应对这类错误进行捕获,一旦这类错误发生,通常应用程序会被终止,仅靠应用程序本身无法恢复;
+
+* Exception 类的错误是可以在应用程序中进行捕获并处理的,通常遇到这种错误,应对其进行处理,使应用程序可以继续正常运行。
+
+### 2\. 运行时异常和一般异常(受检异常)区别是什么?
+
+* 运行时异常包括 RuntimeException 类及其子类,表示 JVM 在运行期间可能出现的异常。 Java 编译器不会检查运行时异常。
+
+* 受检异常是Exception 中除 RuntimeException 及其子类之外的异常。 Java 编译器会检查受检异常。
+
+* **RuntimeException异常和受检异常之间的区别**:是否强制要求调用者必须处理此异常,如果强制要求调用者必须进行处理,那么就使用受检异常,否则就选择非受检异常(RuntimeException)。一般来讲,如果没有特殊的要求,我们建议使用RuntimeException异常。
+
+### 3\. JVM 是如何处理异常的?
+
+* 在一个方法中如果发生异常,这个方法会创建一个异常对象,并转交给 JVM,该异常对象包含异常名称,异常描述以及异常发生时应用程序的状态。创建异常对象并转交给 JVM 的过程称为抛出异常。可能有一系列的方法调用,最终才进入抛出异常的方法,这一系列方法调用的有序列表叫做调用栈。
+
+* JVM 会顺着调用栈去查找看是否有可以处理异常的代码,如果有,则调用异常处理代码。当 JVM 发现可以处理异常的代码时,会把发生的异常传递给它。如果 JVM 没有找到可以处理该异常的代码块,JVM 就会将该异常转交给默认的异常处理器(默认处理器为 JVM 的一部分),默认异常处理器打印出异常信息并终止应用程序。
+
+### 4\. throw 和 throws 的区别是什么?
+
+* Java 中的异常处理除了包括捕获异常和处理异常之外,还包括声明异常和拋出异常,可以通过 throws 关键字在方法上声明该方法要拋出的异常,或者在方法内部通过 throw 拋出异常对象。
+
+**throws 关键字和 throw 关键字在使用上的几点区别如下**:
+
+* throw 关键字用在方法内部,只能用于抛出一种异常,用来抛出方法或代码块中的异常,受查异常和非受查异常都可以被抛出。
+* throws 关键字用在方法声明上,可以抛出多个异常,用来标识该方法可能抛出的异常列表。一个方法用 throws 标识了可能抛出的异常列表,调用该方法的方法中必须包含可处理异常的代码,否则也要在方法签名中用 throws 关键字声明相应的异常。
+
+### 5\. final、finally、finalize 有什么区别?
+
+* final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。
+* finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
+* finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,Java 中允许使用 finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。
+
+### 6\. NoClassDefFoundError 和 ClassNotFoundException 区别?
+
+* NoClassDefFoundError 是一个 Error 类型的异常,是由 JVM 引起的,不应该尝试捕获这个异常。
+
+* 引起该异常的原因是 JVM 或 ClassLoader 尝试加载某类时在内存中找不到该类的定义,该动作发生在运行期间,即编译时该类存在,但是在运行时却找不到了,可能是变异后被删除了等原因导致;
+
+* ClassNotFoundException 是一个受查异常,需要显式地使用 try-catch 对其进行捕获和处理,或在方法签名中用 throws 关键字进行声明。当使用 Class.forName, ClassLoader.loadClass 或 ClassLoader.findSystemClass 动态加载类到内存的时候,通过传入的类路径参数没有找到该类,就会抛出该异常;另一种抛出该异常的可能原因是某个类已经由一个类加载器加载至内存中,另一个加载器又尝试去加载它。
+
+### 7\. try-catch-finally 中哪个部分可以省略?
+
+* 答:catch 可以省略
+
+**原因**
+
+* 更为严格的说法其实是:try只适合处理运行时异常,try+catch适合处理运行时异常+普通异常。也就是说,如果你只用try去处理普通异常却不加以catch处理,编译是通不过的,因为编译器硬性规定,普通异常如果选择捕获,则必须用catch显示声明以便进一步处理。而运行时异常在编译时没有如此规定,所以catch可以省略,你加上catch编译器也觉得无可厚非。
+
+* 理论上,编译器看任何代码都不顺眼,都觉得可能有潜在的问题,所以你即使对所有代码加上try,代码在运行期时也只不过是在正常运行的基础上加一层皮。但是你一旦对一段代码加上try,就等于显示地承诺编译器,对这段代码可能抛出的异常进行捕获而非向上抛出处理。如果是普通异常,编译器要求必须用catch捕获以便进一步处理;如果运行时异常,捕获然后丢弃并且+finally扫尾处理,或者加上catch捕获以便进一步处理。
+
+* 至于加上finally,则是在不管有没捕获异常,都要进行的“扫尾”处理。
+
+### 8\. try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
+
+* 答:会执行,在 return 前执行。
+
+* **注意**:在 finally 中改变返回值的做法是不好的,因为如果存在 finally 代码块,try中的 return 语句不会立马返回调用者,而是记录下返回值待 finally 代码块执行完毕之后再向调用者返回其值,然后如果在 finally 中修改了返回值,就会返回修改后的值。显然,在 finally 中返回或者修改返回值会对程序造成很大的困扰,C#中直接用编译错误的方式来阻止程序员干这种龌龊的事情,Java 中也可以通过提升编译器的语法检查级别来产生警告或错误。
+
+**代码示例1:**
+
+public static int getInt() {
+ int a = 10;
+ try {
+ System.out.println(a / 0);
+ a = 20;
+ } catch (ArithmeticException e) {
+ a = 30;
+ return a;
+ /*
+ * return a 在程序执行到这一步的时候,这里不是return a 而是 return 30;这个返回路径就形成了
+ * 但是呢,它发现后面还有finally,所以继续执行finally的内容,a=40
+ * 再次回到以前的路径,继续走return 30,形成返回路径之后,这里的a就不是a变量了,而是常量30
+ */
+ } finally {
+ a = 40;
+ }
+ return a;
+}
+复制代码
+
+* 执行结果:30
+
+**代码示例2:**
+
+public static int getInt() {
+ int a = 10;
+ try {
+ System.out.println(a / 0);
+ a = 20;
+ } catch (ArithmeticException e) {
+ a = 30;
+ return a;
+ } finally {
+ a = 40;
+ //如果这样,就又重新形成了一条返回路径,由于只能通过1个return返回,所以这里直接返回40
+ return a;
+ }
+
+}
+复制代码
+
+* 执行结果:40
+
+### 9\. 类 ExampleA 继承 Exception,类 ExampleB 继承ExampleA。
+
+* 有如下代码片断:
+
+try {
+ throw new ExampleB("b")
+} catch(ExampleA e){
+ System.out.println("ExampleA");
+} catch(Exception e){
+ System.out.println("Exception");
+}
+复制代码
+
+* 请问执行此段代码的输出是什么?
+
+* **答**:输出:ExampleA。(根据里氏代换原则[能使用父类型的地方一定能使用子类型],抓取 ExampleA 类型异常的 catch 块能够抓住 try 块中抛出的 ExampleB 类型的异常)
+
+* 面试题 - 说出下面代码的运行结果。(此题的出处是《Java 编程思想》一书)
+
+class Annoyance extends Exception {
+}
+class Sneeze extends Annoyance {
+}
+class Human {
+ public static void main(String[] args)
+ throws Exception {
+ try {
+ try {
+ throw new Sneeze();
+ } catch ( Annoyance a ) {
+ System.out.println("Caught Annoyance");
+ throw a;
+ }
+ } catch ( Sneeze s ) {
+ System.out.println("Caught Sneeze");
+ return ;
+ } finally {
+ System.out.println("Hello World!");
+ }
+ }
+}
+复制代码
+
+* 结果
+
+Caught Annoyance
+Caught Sneeze
+Hello World!
+复制代码
+### 10\. 常见的 RuntimeException 有哪些?
+
+* ClassCastException(类转换异常)
+* IndexOutOfBoundsException(数组越界)
+* NullPointerException(空指针)
+* ArrayStoreException(数据存储异常,操作数组时类型不一致)
+* 还有IO操作的BufferOverflowException异常
+
+### 11\. Java常见异常有哪些
+
+* java.lang.IllegalAccessError:违法访问错误。当一个应用试图访问、修改某个类的域(Field)或者调用其方法,但是又违反域或方法的可见性声明,则抛出该异常。
+
+* java.lang.InstantiationError:实例化错误。当一个应用试图通过Java的new操作符构造一个抽象类或者接口时抛出该异常.
+
+* java.lang.OutOfMemoryError:内存不足错误。当可用内存不足以让Java虚拟机分配给一个对象时抛出该错误。
+
+* java.lang.StackOverflowError:堆栈溢出错误。当一个应用递归调用的层次太深而导致堆栈溢出或者陷入死循环时抛出该错误。
+
+* java.lang.ClassCastException:类造型异常。假设有类A和B(A不是B的父类或子类),O是A的实例,那么当强制将O构造为类B的实例时抛出该异常。该异常经常被称为强制类型转换异常。
+
+* java.lang.ClassNotFoundException:找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。
+
+* java.lang.ArithmeticException:算术条件异常。譬如:整数除零等。
+
+* java.lang.ArrayIndexOutOfBoundsException:数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。
+
+* java.lang.IndexOutOfBoundsException:索引越界异常。当访问某个序列的索引值小于0或大于等于序列大小时,抛出该异常。
+
+* java.lang.InstantiationException:实例化异常。当试图通过newInstance()方法创建某个类的实例,而该类是一个抽象类或接口时,抛出该异常。
+
+* java.lang.NoSuchFieldException:属性不存在异常。当访问某个类的不存在的属性时抛出该异常。
+
+* java.lang.NoSuchMethodException:方法不存在异常。当访问某个类的不存在的方法时抛出该异常。-
+
+* java.lang.NullPointerException:空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等。
+
+* java.lang.NumberFormatException:数字格式异常。当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常。
+
+* java.lang.StringIndexOutOfBoundsException:字符串索引越界异常。当使用索引值访问某个字符串中的字符,而该索引值小于0或大于等于序列大小时,抛出该异常。
+
+## Java异常处理最佳实践
+
+* 在 Java 中处理异常并不是一个简单的事情。不仅仅初学者很难理解,即使一些有经验的开发者也需要花费很多时间来思考如何处理异常,包括需要处理哪些异常,怎样处理等等。这也是绝大多数开发团队都会制定一些规则来规范进行异常处理的原因。而团队之间的这些规范往往是截然不同的。
+
+* 本文给出几个被很多团队使用的异常处理最佳实践。
+
+### 1\. 在 finally 块中清理资源或者使用 try-with-resource 语句
+
+* 当使用类似InputStream这种需要使用后关闭的资源时,一个常见的错误就是在try块的最后关闭资源。
+
+public void doNotCloseResourceInTry() {
+ FileInputStream inputStream = null;
+ try {
+ File file = new File("./tmp.txt");
+ inputStream = new FileInputStream(file);
+ // use the inputStream to read a file
+ // do NOT do this
+ inputStream.close();
+ } catch (FileNotFoundException e) {
+ log.error(e);
+ } catch (IOException e) {
+ log.error(e);
+ }
+}
+复制代码
+
+* 问题就是,只有没有异常抛出的时候,这段代码才可以正常工作。try 代码块内代码会正常执行,并且资源可以正常关闭。但是,使用 try 代码块是有原因的,一般调用一个或多个可能抛出异常的方法,而且,你自己也可能会抛出一个异常,这意味着代码可能不会执行到 try 代码块的最后部分。结果就是,你并没有关闭资源。
+
+所以,你应该把清理工作的代码放到 finally 里去,或者使用 try-with-resource 特性。
+
+#### 1.1 使用 finally 代码块
+
+* 与前面几行 try 代码块不同,finally 代码块总是会被执行。不管 try 代码块成功执行之后还是你在 catch 代码块中处理完异常后都会执行。因此,你可以确保你清理了所有打开的资源。
+
+public void closeResourceInFinally() {
+ FileInputStream inputStream = null;
+ try {
+ File file = new File("./tmp.txt");
+ inputStream = new FileInputStream(file);
+ // use the inputStream to read a file
+ } catch (FileNotFoundException e) {
+ log.error(e);
+ } finally {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ log.error(e);
+ }
+ }
+ }
+}
+复制代码
+#### 1.2 Java 7 的 try-with-resource 语法
+
+* 如果你的资源实现了 AutoCloseable 接口,你可以使用这个语法。大多数的 Java 标准资源都继承了这个接口。当你在 try 子句中打开资源,资源会在 try 代码块执行后或异常处理后自动关闭。
+
+public void automaticallyCloseResource() {
+ File file = new File("./tmp.txt");
+ try (FileInputStream inputStream = new FileInputStream(file);) {
+ // use the inputStream to read a file
+ } catch (FileNotFoundException e) {
+ log.error(e);
+ } catch (IOException e) {
+ log.error(e);
+ }
+}
+
+复制代码
+
+
+
+### 2\. 优先明确的异常
+
+* 你抛出的异常越明确越好,永远记住,你的同事或者几个月之后的你,将会调用你的方法并且处理异常。
+
+* 因此需要保证提供给他们尽可能多的信息。这样你的 API 更容易被理解。你的方法的调用者能够更好的处理异常并且避免额外的检查。
+
+* 因此,总是尝试寻找最适合你的异常事件的类,例如,抛出一个 NumberFormatException 来替换一个 IllegalArgumentException 。避免抛出一个不明确的异常。
+
+public void doNotDoThis() throws Exception {
+ ...
+}
+public void doThis() throws NumberFormatException {
+ ...
+}
+
+复制代码
+
+
+
+### 3\. 对异常进行文档说明
+
+* 当在方法上声明抛出异常时,也需要进行文档说明。目的是为了给调用者提供尽可能多的信息,从而可以更好地避免或处理异常。
+ 在 Javadoc 添加 @throws 声明,并且描述抛出异常的场景。
+
+public void doSomething(String input) throws MyBusinessException {
+ ...
+}
+
+复制代码
+### 4\. 使用描述性消息抛出异常
+
+* 在抛出异常时,需要尽可能精确地描述问题和相关信息,这样无论是打印到日志中还是在监控工具中,都能够更容易被人阅读,从而可以更好地定位具体错误信息、错误的严重程度等。
+
+* 但这里并不是说要对错误信息长篇大论,因为本来 Exception 的类名就能够反映错误的原因,因此只需要用一到两句话描述即可。
+
+* 如果抛出一个特定的异常,它的类名很可能已经描述了这种错误。所以,你不需要提供很多额外的信息。一个很好的例子是 NumberFormatException 。当你以错误的格式提供 String 时,它将被 java.lang.Long 类的构造函数抛出。
+
+try {
+ new Long("xyz");
+} catch (NumberFormatException e) {
+ log.error(e);
+}
+
+复制代码
+### 5\. 优先捕获最具体的异常
+
+* 大多数 IDE 都可以帮助你实现这个最佳实践。当你尝试首先捕获较不具体的异常时,它们会报告无法访问的代码块。
+
+* 但问题在于,只有匹配异常的第一个 catch 块会被执行。 因此,如果首先捕获 IllegalArgumentException ,则永远不会到达应该处理更具体的 NumberFormatException 的 catch 块,因为它是 IllegalArgumentException 的子类。
+
+* 总是优先捕获最具体的异常类,并将不太具体的 catch 块添加到列表的末尾。
+
+* 你可以在下面的代码片断中看到这样一个 try-catch 语句的例子。 第一个 catch 块处理所有 NumberFormatException 异常,第二个处理所有非 NumberFormatException 异常的IllegalArgumentException 异常。
+
+public void catchMostSpecificExceptionFirst() {
+ try {
+ doSomething("A message");
+ } catch (NumberFormatException e) {
+ log.error(e);
+ } catch (IllegalArgumentException e) {
+ log.error(e)
+ }
+}
+
+复制代码
+
+
+
+### 6\. 不要捕获 Throwable 类
+
+* Throwable 是所有异常和错误的超类。你可以在 catch 子句中使用它,但是你永远不应该这样做!
+
+* 如果在 catch 子句中使用 Throwable ,它不仅会捕获所有异常,也将捕获所有的错误。JVM 抛出错误,指出不应该由应用程序处理的严重问题。 典型的例子是 OutOfMemoryError 或者 StackOverflowError 。两者都是由应用程序控制之外的情况引起的,无法处理。
+
+* 所以,最好不要捕获 Throwable ,除非你确定自己处于一种特殊的情况下能够处理错误。
+
+public void doNotCatchThrowable() {
+ try {
+ // do something
+ } catch (Throwable t) {
+ // don't do this!
+ }
+}
+
+复制代码
+### 7\. 不要忽略异常
+
+* 很多时候,开发者很有自信不会抛出异常,因此写了一个catch块,但是没有做任何处理或者记录日志。
+
+public void doNotIgnoreExceptions() {
+ try {
+ // do something
+ } catch (NumberFormatException e) {
+ // this will never happen
+ }
+}
+
+复制代码
+
+* 但现实是经常会出现无法预料的异常,或者无法确定这里的代码未来是不是会改动(删除了阻止异常抛出的代码),而此时由于异常被捕获,使得无法拿到足够的错误信息来定位问题。
+
+* 合理的做法是至少要记录异常的信息。
+
+public void logAnException() {
+ try {
+ // do something
+ } catch (NumberFormatException e) {
+ log.error("This should never happen: " + e);
+ }
+}
+
+复制代码
+### 8\. 不要记录并抛出异常
+
+* 这可能是本文中最常被忽略的最佳实践。可以发现很多代码甚至类库中都会有捕获异常、记录日志并再次抛出的逻辑。如下:
+
+try {
+ new Long("xyz");
+} catch (NumberFormatException e) {
+ log.error(e);
+ throw e;
+}
+
+复制代码
+
+* 这个处理逻辑看着是合理的。但这经常会给同一个异常输出多条日志。如下:
+
+17:44:28,945 ERROR TestExceptionHandling:65 - java.lang.NumberFormatException: For input string: "xyz"
+Exception in thread "main" java.lang.NumberFormatException: For input string: "xyz"
+at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
+at java.lang.Long.parseLong(Long.java:589)
+at java.lang.Long.(Long.java:965)
+at com.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHandling.java:63)
+at com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58)
+
+复制代码
+
+* 如上所示,后面的日志也没有附加更有用的信息。如果想要提供更加有用的信息,那么可以将异常包装为自定义异常。
+
+public void wrapException(String input) throws MyBusinessException {
+ try {
+ // do something
+ } catch (NumberFormatException e) {
+ throw new MyBusinessException("A message that describes the error.", e);
+ }
+}
+
+复制代码
+
+* 因此,仅仅当想要处理异常时才去捕获,否则只需要在方法签名中声明让调用者去处理。
+
+### 9\. 包装异常时不要抛弃原始的异常
+
+* 捕获标准异常并包装为自定义异常是一个很常见的做法。这样可以添加更为具体的异常信息并能够做针对的异常处理。
+ 在你这样做时,请确保将原始异常设置为原因(注:参考下方代码 NumberFormatException e 中的原始异常 e )。Exception 类提供了特殊的构造函数方法,它接受一个 Throwable 作为参数。否则,你将会丢失堆栈跟踪和原始异常的消息,这将会使分析导致异常的异常事件变得困难。
+
+public void wrapException(String input) throws MyBusinessException {
+ try {
+ // do something
+ } catch (NumberFormatException e) {
+ throw new MyBusinessException("A message that describes the error.", e);
+ }
+}
+
+复制代码
+
+
+
+### 10\. 不要使用异常控制程序的流程
+
+* 不应该使用异常控制应用的执行流程,例如,本应该使用if语句进行条件判断的情况下,你却使用异常处理,这是非常不好的习惯,会严重影响应用的性能。
+
+### 11\. 使用标准异常
+
+* 如果使用内建的异常可以解决问题,就不要定义自己的异常。Java API 提供了上百种针对不同情况的异常类型,在开发中首先尽可能使用 Java API 提供的异常,如果标准的异常不能满足你的要求,这时候创建自己的定制异常。尽可能得使用标准异常有利于新加入的开发者看懂项目代码。
+
+### 12\. 异常会影响性能
+
+* 异常处理的性能成本非常高,每个 Java 程序员在开发时都应牢记这句话。创建一个异常非常慢,抛出一个异常又会消耗1~5ms,当一个异常在应用的多个层级之间传递时,会拖累整个应用的性能。
+
+ * 仅在异常情况下使用异常;
+ * 在可恢复的异常情况下使用异常;
+* 尽管使用异常有利于 Java 开发,但是在应用中最好不要捕获太多的调用栈,因为在很多情况下都不需要打印调用栈就知道哪里出错了。因此,异常消息应该提供恰到好处的信息。
+
+### 13\. 总结
+
+* 综上所述,当你抛出或捕获异常的时候,有很多不同的情况需要考虑,而且大部分事情都是为了改善代码的可读性或者 API 的可用性。
+
+* 异常不仅仅是一个错误控制机制,也是一个通信媒介。因此,为了和同事更好的合作,一个团队必须要制定出一个最佳实践和规则,只有这样,团队成员才能理解这些通用概念,同时在工作中使用它。
+
+### 异常处理-阿里巴巴Java开发手册
+
+1. 【强制】Java 类库中定义的可以通过预检查方式规避的RuntimeException异常不应该通过catch 的方式来处理,比如:NullPointerException,IndexOutOfBoundsException等等。 说明:无法通过预检查的异常除外,比如,在解析字符串形式的数字时,可能存在数字格式错误,不得不通过catch NumberFormatException来实现。 正例:if (obj != null) {…} 反例:try { obj.method(); } catch (NullPointerException e) {…}
+
+2. 【强制】异常不要用来做流程控制,条件控制。 说明:异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多。
+
+3. 【强制】catch时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的catch尽可能进行区分异常类型,再做对应的异常处理。 说明:对大段代码进行try-catch,使程序无法根据不同的异常做出正确的应激反应,也不利于定位问题,这是一种不负责任的表现。 正例:用户注册的场景中,如果用户输入非法字符,或用户名称已存在,或用户输入密码过于简单,在程序上作出分门别类的判断,并提示给用户。
+
+4. 【强制】捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。
+
+5. 【强制】有try块放到了事务代码中,catch异常后,如果需要回滚事务,一定要注意手动回滚事务。
+
+6. 【强制】finally块必须对资源对象、流对象进行关闭,有异常也要做try-catch。 说明:如果JDK7及以上,可以使用try-with-resources方式。
+
+7. 【强制】不要在finally块中使用return。 说明:try块中的return语句执行成功后,并不马上返回,而是继续执行finally块中的语句,如果此处存在return语句,则在此直接返回,无情丢弃掉try块中的返回点。 反例:
+
+ private int x = 0;
+ public int checkReturn() {
+ try {
+ // x等于1,此处不返回
+ return ++x;
+ } finally {
+ // 返回的结果是2
+ return ++x;
+ }
+ }
+
+复制代码
+
+1. 【强制】捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。 说明:如果预期对方抛的是绣球,实际接到的是铅球,就会产生意外情况。
+
+2. 【强制】在调用RPC、二方包、或动态生成类的相关方法时,捕捉异常必须使用Throwable类来进行拦截。 说明:通过反射机制来调用方法,如果找不到方法,抛出NoSuchMethodException。什么情况会抛出NoSuchMethodError呢?二方包在类冲突时,仲裁机制可能导致引入非预期的版本使类的方法签名不匹配,或者在字节码修改框架(比如:ASM)动态创建或修改类时,修改了相应的方法签名。这些情况,即使代码编译期是正确的,但在代码运行期时,会抛出NoSuchMethodError。
+
+3. 【推荐】方法的返回值可以为null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回null值。 说明:本手册明确防止NPE是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回null的情况。
+
+4. 【推荐】防止NPE,是程序员的基本修养,注意NPE产生的场景: 1) 返回类型为基本数据类型,return包装数据类型的对象时,自动拆箱有可能产生NPE。 反例:public int f() { return Integer对象}, 如果为null,自动解箱抛NPE。 2) 数据库的查询结果可能为null。 3) 集合里的元素即使isNotEmpty,取出的数据元素也可能为null。 4) 远程调用返回对象时,一律要求进行空指针判断,防止NPE。 5) 对于Session中获取的数据,建议进行NPE检查,避免空指针。 6) 级联调用obj.getA().getB().getC();一连串调用,易产生NPE。
+ 正例:使用JDK8的Optional类来防止NPE问题。
+
+5. 【推荐】定义时区分unchecked / checked 异常,避免直接抛出new RuntimeException(),更不允许抛出Exception或者Throwable,应使用有业务含义的自定义异常。推荐业界已定义过的自定义异常,如:DAOException / ServiceException等。
+
+6. 【参考】对于公司外的http/api开放接口必须使用“错误码”;而应用内部推荐异常抛出;跨应用间RPC调用优先考虑使用Result方式,封装isSuccess()方法、“错误码”、“错误简短信息”。 说明:关于RPC方法返回方式使用Result方式的理由: 1)使用抛异常返回方式,调用方如果没有捕获到就会产生运行时错误。 2)如果不加栈信息,只是new自定义异常,加入自己的理解的error message,对于调用端解决问题的帮助不会太多。如果加了栈信息,在频繁调用出错的情况下,数据序列化和传输的性能损耗也是问题。
+
+7. 【参考】避免出现重复的代码(Don’t Repeat Yourself),即DRY原则。 说明:随意复制和粘贴代码,必然会导致代码的重复,在以后需要修改时,需要修改所有的副本,容易遗漏。必要时抽取共性方法,或者抽象公共类,甚至是组件化。 正例:一个类中有多个public方法,都需要进行数行相同的参数校验操作,这个时候请抽取:
+ private boolean checkParam(DTO dto) {…}
+
+作者:小杰要吃蛋
+链接:https://juejin.cn/post/6844904128959741965
+来源:稀土掘金
+著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
\ No newline at end of file
diff --git "a/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\350\231\232\346\213\237\346\234\272.md" "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\350\231\232\346\213\237\346\234\272.md"
new file mode 100644
index 0000000..382dac4
--- /dev/null
+++ "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\350\231\232\346\213\237\346\234\272.md"
@@ -0,0 +1,627 @@
+
+
+## Java内存模型
+
+### 我们开发人员编写的Java代码是怎么让电脑认识的
+
+* 首先先了解电脑是二进制的系统,他只认识 01010101
+
+* 比如我们经常要编写 HelloWord.java 电脑是怎么认识运行的
+
+* HelloWord.java是我们程序员编写的,我们人可以认识,但是电脑不认识
+
+**Java文件编译的过程**
+
+1. 程序员编写的.java文件
+2. 由javac编译成字节码文件.class:(为什么编译成class文件,因为JVM只认识.class文件)
+3. 在由JVM编译成电脑认识的文件 (对于电脑系统来说 文件代表一切)
+
+`(这是一个大概的观念 抽象画的概念)`
+
+
+### 为什么说java是跨平台语言
+
+* 这个夸平台是中间语言(JVM)实现的夸平台
+* Java有JVM从软件层面屏蔽了底层硬件、指令层面的细节让他兼容各种系统
+
+`难道 C 和 C++ 不能夸平台吗 其实也可以` `C和C++需要在编译器层面去兼容不同操作系统的不同层面,写过C和C++的就知道不同操作系统的有些代码是不一样`
+
+### Jdk和Jre和JVM的区别
+
+* Jdk包括了Jre和Jvm,Jre包括了Jvm
+
+* Jdk是我们编写代码使用的开发工具包
+
+* Jre 是Java的运行时环境,他大部分都是 C 和 C++ 语言编写的,他是我们在编译java时所需要的基础的类库
+
+* Jvm俗称Java虚拟机,他是java运行环境的一部分,它虚构出来的一台计算机,在通过在实际的计算机上仿真模拟各种计算机功能来实现Java应用程序
+
+* 看Java官方的图片,Jdk中包括了Jre,Jre中包括了JVM
+
+ 
+
+### 说一下 JVM由那些部分组成,运行流程是什么?
+
+
+
+* JVM包含两个子系统和两个组件: 两个子系统为Class loader(类装载)、Execution engine(执行引擎); 两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口)。
+
+ * Class loader(类装载):根据给定的全限定名类名(如:java.lang.Object)来装载class文件到Runtime data area中的method area。
+
+ * Execution engine(执行引擎):执行classes中的指令。
+
+ * Native Interface(本地接口):与native libraries交互,是其它编程语言交互的接口。
+
+ * Runtime data area(运行时数据区域):这就是我们常说的JVM的内存。
+
+* **流程** :首先通过编译器把 Java 代码转换成字节码,类加载器(ClassLoader)再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的方法区内,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。
+
+### 说一下 JVM 运行时数据区
+
+* Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存区域划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有些区域随着虚拟机进程的启动而存在,有些区域则是依赖线程的启动和结束而建立和销毁。Java 虚拟机所管理的内存被划分为如下几个区域:
+
+`简单的说就是我们java运行时的东西是放在那里的`
+
+
+
+* 程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成;
+
+ `为什么要线程计数器?因为线程是不具备记忆功能`
+
+* Java 虚拟机栈(Java Virtual Machine Stacks):每个方法在执行的同时都会在Java 虚拟机栈中创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息;
+
+ `栈帧就是Java虚拟机栈中的下一个单位`
+
+* 本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的;
+
+ `Native 关键字修饰的方法是看不到的,Native 方法的源码大部分都是 C和C++ 的代码`
+
+* Java 堆(Java Heap):Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存;
+
+* 方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。
+
+`后面有详细的说明JVM 运行时数据区`
+
+### 详细的介绍下程序计数器?(重点理解)
+
+1. 程序计数器是一块较小的内存空间,它可以看作是:保存当前线程所正在执行的字节码指令的地址(行号)
+
+2. 由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储。称之为“线程私有”的内存。程序计数器内存区域是虚拟机中唯一没有规定OutOfMemoryError情况的区域。
+
+ `总结:也可以把它叫做线程计数器`
+
+* **例子**:在java中最小的执行单位是线程,线程是要执行指令的,执行的指令最终操作的就是我们的电脑,就是 CPU。在CPU上面去运行,有个非常不稳定的因素,叫做调度策略,这个调度策略是时基于时间片的,也就是当前的这一纳秒是分配给那个指令的。
+
+* **假如**:
+
+ * 线程A在看直播 
+
+ * 突然,线程B来了一个视频电话,就会抢夺线程A的时间片,就会打断了线程A,线程A就会挂起 
+
+ * 然后,视频电话结束,这时线程A究竟该干什么? (线程是最小的执行单位,他不具备记忆功能,他只负责去干,那这个记忆就由:**程序计数器来记录**) 
+
+### 详细介绍下Java虚拟机栈?(重点理解)
+
+1. Java虚拟机是线程私有的,它的生命周期和线程相同。
+2. 虚拟机栈描述的是Java方法执行的内存模型:`每个方法在执行的同时`都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
+
+* **解释**:虚拟机栈中是有单位的,单位就是**栈帧**,一个方法一个**栈帧**。一个**栈帧**中他又要存储,局部变量,操作数栈,动态链接,出口等。
+
+**解析栈帧:**
+
+1. 局部变量表:是用来存储我们临时8个基本数据类型、对象引用地址、returnAddress类型。(returnAddress中保存的是return后要执行的字节码的指令地址。)
+2. 操作数栈:操作数栈就是用来操作的,例如代码中有个 i = 6*6,他在一开始的时候就会进行操作,读取我们的代码,进行计算后再放入局部变量表中去
+3. 动态链接:假如我方法中,有个 service.add()方法,要链接到别的方法中去,这就是动态链接,存储链接的地方。
+4. 出口:出口是什呢,出口正常的话就是return 不正常的话就是抛出异常落
+
+#### 一个方法调用另一个方法,会创建很多栈帧吗?
+
+* 答:会创建。如果一个栈中有动态链接调用别的方法,就会去创建新的栈帧,栈中是由顺序的,一个栈帧调用另一个栈帧,另一个栈帧就会排在调用者下面
+
+#### 栈指向堆是什么意思?
+
+* 栈指向堆是什么意思,就是栈中要使用成员变量怎么办,栈中不会存储成员变量,只会存储一个应用地址
+
+#### 递归的调用自己会创建很多栈帧吗?
+
+* 答:递归的话也会创建多个栈帧,就是在栈中一直从上往下排下去
+
+### 你能给我详细的介绍Java堆吗?(重点理解)
+
+* java堆(Java Heap)是java虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。
+* 在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
+* java堆是垃圾收集器管理的主要区域,因此也被成为“GC堆”。
+* 从内存回收角度来看java堆可分为:新生代和老生代。
+* 从内存分配的角度看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区。
+* 无论怎么划分,都与存放内容无关,无论哪个区域,存储的都是对象实例,进一步的划分都是为了更好的回收内存,或者更快的分配内存。
+* 根据Java虚拟机规范的规定,java堆可以处于物理上不连续的内存空间中。当前主流的虚拟机都是可扩展的(通过 -Xmx 和 -Xms 控制)。如果堆中没有内存可以完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
+
+### 能不能解释一下本地方法栈?
+
+1. 本地方法栈很好理解,他很栈很像,只不过方法上带了 native 关键字的栈字
+2. 它是虚拟机栈为虚拟机执行Java方法(也就是字节码)的服务方法
+3. native关键字的方法是看不到的,必须要去oracle官网去下载才可以看的到,而且native关键字修饰的大部分源码都是C和C++的代码。
+4. 同理可得,本地方法栈中就是C和C++的代码
+
+### 能不能解释一下方法区(重点理解)
+
+1. 方法区是所有线程共享的内存区域,它用于存储已被Java虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
+2. 它有个别命叫Non-Heap(非堆)。当方法区无法满足内存分配需求时,抛出OutOfMemoryError异常。
+
+### 什么是JVM字节码执行引擎
+
+* 虚拟机核心的组件就是执行引擎,它负责执行虚拟机的字节码,一般户先进行编译成机器码后执行。
+
+* “虚拟机”是一个相对于“物理机”的概念,虚拟机的字节码是不能直接在物理机上运行的,需要JVM字节码执行引擎- 编译成机器码后才可在物理机上执行。
+
+### 你听过直接内存吗?
+
+* 直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致 OutOfMemoryError 异常出现,所以我们放到这里一起讲解。
+* 我的理解就是直接内存是基于物理内存和Java虚拟机内存的中间内存
+
+### 知道垃圾收集系统吗?
+
+* 程序在运行过程中,会产生大量的内存垃圾(一些没有引用指向的内存对象都属于内存垃圾,因为这些对象已经无法访问,程序用不了它们了,对程序而言它们已经死亡),为了确保程序运行时的性能,java虚拟机在程序运行的过程中不断地进行自动的垃圾回收(GC)。
+
+* 垃圾收集系统是Java的核心,也是不可少的,Java有一套自己进行垃圾清理的机制,开发人员无需手工清理
+
+* 有一部分原因就是因为Java垃圾回收系统的强大导致Java领先市场
+
+### 堆栈的区别是什么?
+
+> | 对比 | JVM堆 | JVM栈 |
+> | --- | --- | --- |
+> | 物理地址 | 堆的物理地址分配对对象是不连续的。因此性能慢些。在GC的时候也要考虑到不连续的分配,所以有各种算法。比如,标记-消除,复制,标记-压缩,分代(即新生代使用复制算法,老年代使用标记——压缩) | 栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快。 |
+> | 内存分别 | 堆因为是不连续的,所以分配的内存是在运行期确认的,因此大小不固定。一般堆大小远远大于栈。 | 栈是连续的,所以分配的内存大小要在编译期就确认,大小是固定的。 |
+> | 存放的内容 | 堆存放的是对象的实例和数组。因此该区更关注的是数据的存储 | 栈存放:局部变量,操作数栈,返回结果。该区更关注的是程序方法的执行。 |
+> | 程序的可见度 | 堆对于整个应用程序都是共享、可见的。 | 栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。 |
+
+* 注意:
+ * 静态变量放在方法区
+ * 静态的对象还是放在堆。
+
+### 深拷贝和浅拷贝
+
+* 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
+* 深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,
+* 浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。
+* 深复制:在计算机中开辟一块**新的内存地址**用于存放复制的对象。
+
+### Java会存在内存泄漏吗?请说明为什么?
+
+* 内存泄漏是指不再被使用的对象或者变量一直被占据在内存中。理论上来说,Java是有GC垃圾回收机制的,也就是说,不再被使用的对象,会被GC自动回收掉,自动从内存中清除。
+
+* 但是,即使这样,Java也还是存在着内存泄漏的情况,java导致内存泄露的原因很明确:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,`尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收`,这就是java中内存泄露的发生场景。
+
+## 垃圾回收机制及算法
+
+### 简述Java垃圾回收机制
+
+* 在java中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。
+
+### GC是什么?为什么要GC
+
+* GC 是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java 提供的 GC 功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java 语言没有提供释放已分配内存的显示操作方法。
+
+### 垃圾回收的优点和缺点
+
+* 优点:JVM的垃圾回收器都不需要我们手动处理无引用的对象了,这个就是最大的优点
+
+* 缺点:程序员不能实时的对某个对象或所有对象调用垃圾回收器进行垃圾回收。
+
+### 垃圾回收器的原理是什么?有什么办法手动进行垃圾回收?
+
+* 对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。
+
+* 通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间。
+
+* 可以。程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。
+
+### JVM 中都有哪些引用类型?
+
+* 强引用:发生 gc 的时候不会被回收。
+* 软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。
+* 弱引用:有用但不是必须的对象,在下一次GC时会被回收。
+* 虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用 PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知。
+
+### 怎么判断对象是否可以被回收?
+
+* 垃圾收集器在做垃圾回收的时候,首先需要判定的就是哪些内存是需要被回收的,哪些对象是存活的,是不可以被回收的;哪些对象已经死掉了,需要被回收。
+
+* 一般有两种方法来判断:
+
+ * 引用计数器法:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题;(这个已经淘汰了)
+ * 可达性分析算法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。(市场上用的非常非常广泛)
+
+### Full GC是什么
+
+* 清理整个堆空间—包括年轻代和老年代和永久代
+* 因为Full GC是清理整个堆空间所以Full GC执行速度非常慢,在Java开发中最好保证少触发Full GC
+
+### 对象什么时候可以被垃圾器回收
+
+* 当对象对当前使用这个对象的应用程序变得不可触及的时候,这个对象就可以被回收了。
+* 垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。
+
+### JVM 垃圾回收算法有哪些?
+
+* 标记-清除算法:标记无用对象,然后进行清除回收。缺点:效率不高,无法清除垃圾碎片。
+* 复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不高,只有原来的一半。
+* 标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。
+* 分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。
+
+#### 标记-清除算法
+
+* 标记无用对象,然后进行清除回收。
+
+* 标记-清除算法(Mark-Sweep)是一种常见的基础垃圾收集算法,它将垃圾收集分为两个阶段:
+
+ * 标记阶段:标记出可以回收的对象。
+ * 清除阶段:回收被标记的对象所占用的空间。
+* 标记-清除算法之所以是基础的,是因为后面讲到的垃圾收集算法都是在此算法的基础上进行改进的。
+
+* **优点**:实现简单,不需要对象进行移动。
+
+* **缺点**:标记、清除过程效率低,产生大量不连续的内存碎片,提高了垃圾回收的频率。
+
+* 标记-清除算法的执行的过程如下图所示
+
+
+#### 复制算法
+
+* 为了解决标记-清除算法的效率不高的问题,产生了复制算法。它把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾收集时,遍历当前使用的区域,把存活对象复制到另外一个区域中,最后将当前使用的区域的可回收的对象进行回收。
+
+* **优点**:按顺序分配内存即可,实现简单、运行高效,不用考虑内存碎片。
+
+* **缺点**:可用的内存大小缩小为原来的一半,对象存活率高时会频繁进行复制。
+
+* 复制算法的执行过程如下图所示
+
+
+#### 标记-整理算法
+
+* 在新生代中可以使用复制算法,但是在老年代就不能选择复制算法了,因为老年代的对象存活率会较高,这样会有较多的复制操作,导致效率变低。标记-清除算法可以应用在老年代中,但是它效率不高,在内存回收后容易产生大量内存碎片。因此就出现了一种标记-整理算法(Mark-Compact)算法,与标记-整理算法不同的是,在标记可回收的对象后将所有存活的对象压缩到内存的一端,使他们紧凑的排列在一起,然后对端边界以外的内存进行回收。回收后,已用和未用的内存都各自一边。
+
+* **优点**:解决了标记-清理算法存在的内存碎片问题。
+
+* **缺点**:仍需要进行局部对象移动,一定程度上降低了效率。
+
+* 标记-整理算法的执行过程如下图所示
+
+
+#### 分代收集算法
+
+* 当前商业虚拟机都采用 `分代收集`的垃圾收集算法。分代收集算法,顾名思义是根据对象的`存活周期`将内存划分为几块。一般包括`年轻代`、`老年代`和 `永久代`,如图所示:`(后面有重点讲解)`
+
+
+### JVM中的永久代中会发生垃圾回收吗
+
+* 垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。请参考下Java8:从永久代到元数据区
+ (注:Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区)
+
+## 垃圾收集器以及新生代、老年代、永久代
+
+### 讲一下新生代、老年代、永久代的区别
+
+
+
+* 在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。而新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。
+
+* 新生代中一般保存新出现的对象,所以每次垃圾收集时都发现大批对象死去,只有少量对象存活,便采用了`复制算法`,只需要付出少量存活对象的复制成本就可以完成收集。
+
+* 老年代中一般保存存活了很久的对象,他们存活率高、没有额外空间对它进行分配担保,就必须采用`“标记-清理”或者“标记-整理”`算法。
+
+* 永久代就是JVM的方法区。在这里都是放着一些被虚拟机加载的类信息,静态变量,常量等数据。这个区中的东西比老年代和新生代更不容易回收。
+
+### Minor GC、Major GC、Full GC是什么
+
+1. Minor GC是新生代GC,指的是发生在新生代的垃圾收集动作。由于java对象大都是朝生夕死的,所以Minor GC非常频繁,一般回收速度也比较快。(一般采用复制算法回收垃圾)
+2. Major GC是老年代GC,指的是发生在老年代的GC,通常执行Major GC会连着Minor GC一起执行。Major GC的速度要比Minor GC慢的多。(可采用标记清楚法和标记整理法)
+3. Full GC是清理整个堆空间,包括年轻代和老年代
+
+### Minor GC、Major GC、Full GC区别及触发条件
+
+* **Minor GC 触发条件一般为:**
+
+ 1. eden区满时,触发MinorGC。即申请一个对象时,发现eden区不够用,则触发一次MinorGC。
+ 2. 新创建的对象大小 > Eden所剩空间时触发Minor GC
+* **Major GC和Full GC 触发条件一般为:** `Major GC通常是跟full GC是等价的`
+
+ 1. 每次晋升到老年代的对象平均大小>老年代剩余空间
+
+ 2. MinorGC后存活的对象超过了老年代剩余空间
+
+ 3. 永久代空间不足
+
+ 4. 执行System.gc()
+
+ 5. CMS GC异常
+
+ 6. 堆内存分配很大的对象
+
+### 为什么新生代要分Eden和两个 Survivor 区域?
+
+* 如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC.老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多,所以需要分为Eden和Survivor。
+* Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历15次Minor GC还能在新生代中存活的对象,才会被送到老年代。
+* 设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)
+
+### Java堆老年代( Old ) 和新生代 ( Young ) 的默认比例?
+
+* 默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。
+
+* 其中,新生代 ( Young ) 被细分为 Eden 和 **两个 Survivor 区域**,Edem 和俩个Survivor 区域比例是 = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),
+
+* 但是JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。
+
+### 为什么要这样分代:
+
+* 其实主要原因就是可以根据各个年代的特点进行对象分区存储,更便于回收,采用最适当的收集算法:
+
+ * 新生代中,每次垃圾收集时都发现大批对象死去,只有少量对象存活,便采用了复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
+
+ * 而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须采用“标记-清理”或者“标记-整理”算法。
+
+* 新生代又分为Eden和Survivor (From与To,这里简称一个区)两个区。加上老年代就这三个区。数据会首先分配到Eden区当中(当然也有特殊情况,如果是大对象那么会直接放入到老年代(大对象是指需要大量连续内存空间的java对象)。当Eden没有足够空间的时候就会触发jvm发起一次Minor GC,。如果对象经过一次Minor-GC还存活,并且又能被Survivor空间接受,那么将被移动到Survivor空间当中。并将其年龄设为1,对象在Survivor每熬过一次Minor GC,年龄就加1,当年龄达到一定的程度(默认为15)时,就会被晋升到老年代中了,当然晋升老年代的年龄是可以设置的。
+
+### 什么是垃圾回收器他和垃圾算法有什么区别
+
+* 垃圾收集器是垃圾回收算法(标记清楚法、标记整理法、复制算法、分代算法)的具体实现,不同垃圾收集器、不同版本的JVM所提供的垃圾收集器可能会有很在差别。
+
+### 说一下 JVM 有哪些垃圾回收器?
+
+* 如果说垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。下图展示了7种作用于不同分代的收集器,其中用于回收新生代的收集器包括Serial、PraNew、Parallel Scavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,还有用于回收整个Java堆的G1收集器。不同收集器之间的连线表示它们可以搭配使用。
+
+
+
+> | 垃圾回收器 | 工作区域 | 回收算法 | 工作线程 | 用户线程并行 | 描述 |
+> | --- | --- | --- | --- | --- | --- |
+> | Serial | 新生带 | 复制算法 | 单线程 | 否 | Client模式下默认新生代收集器。简单高效 |
+> | ParNew | 新生带 | 复制算法 | 多线程 | 否 | Serial的多线程版本,Server模式下首选, 可搭配CMS的新生代收集器 |
+> | Parallel Scavenge | 新生带 | 复制算法 | 多线程 | 否 | 目标是达到可控制的吞吐量 |
+> | Serial Old | 老年带 | 标记-整理 | 单线程 | 否 | Serial老年代版本,给Client模式下的虚拟机使用 |
+> | Parallel Old | 老年带 | 标记-整理 | 多线程 | 否 | Parallel Scavenge老年代版本,吞吐量优先 |
+> | | | | | | |
+> | G1 | 新生带 + 老年带 | 标记-整理 + 复制算法 | 多线程 | 是 | JDK1.9默认垃圾收集器 |
+
+* Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点是简单高效;
+* ParNew收集器 (复制算法): 新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;
+* Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;
+* Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本;
+* Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;
+* CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。
+* G1(Garbage First)收集器 ( `标记整理 + 复制算法来回收垃圾` ): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。
+
+### 收集器可以这么分配?(了解就好了)
+
+Serial / Serial Old
+Serial / CMS
+ParNew / Serial Old
+ParNew / CMS
+Parallel Scavenge / Serial Old
+Parallel Scavenge / Parallel Old
+G1
+复制代码
+### 新生代垃圾回收器和老年代垃圾回收器都有哪些?有什么区别?
+
+* 新生代回收器:Serial、ParNew、Parallel Scavenge
+* 老年代回收器:Serial Old、Parallel Old、CMS
+* 整堆回收器:G1
+
+新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。
+
+### 简述分代垃圾回收器是怎么工作的?
+
+* 分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。
+
+* 新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下:
+
+ * 把 Eden + From Survivor 存活的对象放入 To Survivor 区;
+ * 清空 Eden 和 From Survivor 分区;
+ * From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor。
+* 每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代。
+
+* 老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。
+
+## 内存分配策略
+
+### 简述java内存分配与回收策率以及Minor GC和Major GC
+
+* 所谓自动内存管理,最终要解决的也就是内存分配和内存回收两个问题。前面我们介绍了内存回收,这里我们再来聊聊内存分配。
+
+* 对象的内存分配通常是在 Java 堆上分配(随着虚拟机优化技术的诞生,某些场景下也会在栈上分配,后面会详细介绍),对象主要分配在新生代的 Eden 区,如果启动了本地线程缓冲,将按照线程优先在 TLAB 上分配。少数情况下也会直接在老年代上分配。总的来说分配规则不是百分百固定的,其细节取决于哪一种垃圾收集器组合以及虚拟机相关参数有关,但是虚拟机对于内存的分配还是会遵循以下几种「普世」规则:
+
+#### 对象优先在 Eden 区分配
+
+* 多数情况,对象都在新生代 Eden 区分配。当 Eden 区分配没有足够的空间进行分配时,虚拟机将会发起一次 Minor GC。如果本次 GC 后还是没有足够的空间,则将启用分配担保机制在老年代中分配内存。
+ * 这里我们提到 Minor GC,如果你仔细观察过 GC 日常,通常我们还能从日志中发现 Major GC/Full GC。
+ * **Minor GC** 是指发生在新生代的 GC,因为 Java 对象大多都是朝生夕死,所有 Minor GC 非常频繁,一般回收速度也非常快;
+ * **Major GC/Full GC** 是指发生在老年代的 GC,出现了 Major GC 通常会伴随至少一次 Minor GC。Major GC 的速度通常会比 Minor GC 慢 10 倍以上。
+
+#### 为什么大对象直接进入老年代
+
+* 所谓大对象是指需要大量连续内存空间的对象,频繁出现大对象是致命的,会导致在内存还有不少空间的情况下提前触发 GC 以获取足够的连续空间来安置新对象。
+
+* 前面我们介绍过新生代使用的是标记-清除算法来处理垃圾回收的,如果大对象直接在新生代分配就会导致 Eden 区和两个 Survivor 区之间发生大量的内存复制。因此对于大对象都会直接在老年代进行分配。
+
+#### 长期存活对象将进入老年代
+
+* 虚拟机采用分代收集的思想来管理内存,那么内存回收时就必须判断哪些对象应该放在新生代,哪些对象应该放在老年代。因此虚拟机给每个对象定义了一个对象年龄的计数器,如果对象在 Eden 区出生,并且能够被 Survivor 容纳,将被移动到 Survivor 空间中,这时设置对象年龄为 1。对象在 Survivor 区中每「熬过」一次 Minor GC 年龄就加 1,当年龄达到一定程度(默认 15) 就会被晋升到老年代。
+
+## 虚拟机类加载机制
+
+### 简述java类加载机制?
+
+* 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成可以被虚拟机直接使用的java类型。
+
+### 类加载的机制及过程
+
+* 程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化3个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化。
+
+
+##### 1、加载
+
+* 加载指的是将类的class文件读入到内存,并将这些静态数据转换成方法区中的运行时数据结构,并在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口,这个过程需要类加载器参与。
+
+* Java类加载器由JVM提供,是所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。
+
+* 类加载器,可以从不同来源加载类的二进制数据,比如:本地Class文件、Jar包Class文件、网络Class文件等等等。
+
+* 类加载的最终产物就是位于堆中的Class对象(注意不是目标类对象),该对象封装了类在方法区中的数据结构,并且向用户提供了访问方法区数据结构的接口,即Java反射的接口
+
+##### 2、连接过程
+
+* 当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中(意思就是将java类的二进制代码合并到JVM的运行状态之中)。类连接又可分为如下3个阶段。
+
+1. 验证:确保加载的类信息符合JVM规范,没有安全方面的问题。主要验证是否符合Class文件格式规范,并且是否能被当前的虚拟机加载处理。
+
+2. 准备:正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配
+
+3. 解析:虚拟机常量池的符号引用替换为字节引用过程
+
+##### 3、初始化
+
+* 初始化阶段是执行类构造器``() 方法的过程。类构造器``()方法是由编译器自动收藏类中的`所有类变量的赋值动作和静态语句块(static块)中的语句合并产生,代码从上往下执行。`
+
+* 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
+
+* 虚拟机会保证一个类的``() 方法在多线程环境中被正确加锁和同步
+
+`初始化的总结就是:初始化是为类的静态变量赋予正确的初始值`
+
+### 描述一下JVM加载Class文件的原理机制
+
+* Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显式的加载所需要的类。
+
+* 类装载方式,有两种 :
+
+ * 1.隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中,
+
+ * 2.显式装载, 通过class.forname()等方法,显式加载需要的类
+
+* Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到jvm中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销。
+
+### 什么是类加载器,类加载器有哪些?
+
+
+
+* 实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。
+
+* 主要有一下四种类加载器:
+
+ 1. 启动类加载器(Bootstrap ClassLoader)用来加载java核心类库,无法被java程序直接引用。
+ 2. 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
+ 3. 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
+ 4. 用户自定义类加载器,通过继承 java.lang.ClassLoader类的方式实现。
+
+### 说一下类装载的执行过程?
+
+* 类装载分为以下 5 个步骤:
+ * 加载:根据查找路径找到相应的 class 文件然后导入;
+ * 验证:检查加载的 class 文件的正确性;
+ * 准备:给类中的静态变量分配内存空间;
+ * 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址;
+ * 初始化:对静态变量和静态代码块执行初始化工作。
+
+### 什么是双亲委派模型?
+
+* 在介绍双亲委派模型之前先说下类加载器。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立在 JVM 中的唯一性,每一个类加载器,都有一个独立的类名称空间。类加载器就是根据指定全限定名称将 class 文件加载到 JVM 内存,然后再转化为 class 对象。
+
+
+
+* 类加载器分类:
+
+ * 启动类加载器(Bootstrap ClassLoader),是虚拟机自身的一部分,用来加载Java_HOME/lib/目录中的,或者被 -Xbootclasspath 参数所指定的路径中并且被虚拟机识别的类库;
+ * 其他类加载器:
+ * 扩展类加载器(Extension ClassLoader):负责加载\lib\ext目录或Java. ext. dirs系统变量指定的路径中的所有类库;
+ * 应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。
+* 双亲委派模型:如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。
+
+* 总结就是:`当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,由父类去加载,如果此时父类不能加载,反馈给子类,由子类去完成类的加载。`
+
+## JVM调优
+
+### JVM 调优的参数可以在那设置参数值
+
+* 可以在IDEA,Eclipse,工具里设置
+
+* 如果上线了是WAR包的话可以在Tomcat设置
+
+* 如果是Jar包直接 :java -jar 是直接插入JVM命令就好了
+
+ java -Xms1024m -Xmx1024m ...等等等 JVM参数 -jar springboot_app.jar &
+ 复制代码
+
+### 说一下 JVM 调优的工具?
+
+* JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具。
+
+ * jconsole:用于对 JVM 中的内存、线程和类等进行监控; 
+
+ * jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc 变化等。 
+
+### 常用的 JVM 调优的参数都有哪些?
+
+#常用的设置
+-Xms:初始堆大小,JVM 启动的时候,给定堆空间大小。
+
+-Xmx:最大堆大小,JVM 运行过程中,如果初始堆空间不足的时候,最大可以扩展到多少。
+
+-Xmn:设置堆中年轻代大小。整个堆大小=年轻代大小+年老代大小+持久代大小。
+
+-XX:NewSize=n 设置年轻代初始化大小大小
+
+-XX:MaxNewSize=n 设置年轻代最大值
+
+-XX:NewRatio=n 设置年轻代和年老代的比值。如: -XX:NewRatio=3,表示年轻代与年老代比值为 1:3,年轻代占整个年轻代+年老代和的 1/4
+
+-XX:SurvivorRatio=n 年轻代中 Eden 区与两个 Survivor 区的比值。注意 Survivor 区有两个。8表示两个Survivor :eden=2:8 ,即一个Survivor占年轻代的1/10,默认就为8
+
+-Xss:设置每个线程的堆栈大小。JDK5后每个线程 Java 栈大小为 1M,以前每个线程堆栈大小为 256K。
+
+-XX:ThreadStackSize=n 线程堆栈大小
+
+-XX:PermSize=n 设置持久代初始值
+
+-XX:MaxPermSize=n 设置持久代大小
+
+-XX:MaxTenuringThreshold=n 设置年轻带垃圾对象最大年龄。如果设置为 0 的话,则年轻代对象不经过 Survivor 区,直接进入年老代。
+
+#下面是一些不常用的
+
+-XX:LargePageSizeInBytes=n 设置堆内存的内存页大小
+
+-XX:+UseFastAccessorMethods 优化原始类型的getter方法性能
+
+-XX:+DisableExplicitGC 禁止在运行期显式地调用System.gc(),默认启用
+
+-XX:+AggressiveOpts 是否启用JVM开发团队最新的调优成果。例如编译优化,偏向锁,并行年老代收集等,jdk6纸之后默认启动
+
+-XX:+UseBiasedLocking 是否启用偏向锁,JDK6默认启用
+
+-Xnoclassgc 是否禁用垃圾回收
+
+-XX:+UseThreadPriorities 使用本地线程的优先级,默认启用
+
+等等等......
+复制代码
+### JVM的GC收集器设置
+
+* -xx:+Use xxx GC
+ * xxx 代表垃圾收集器名称
+
+-XX:+UseSerialGC:设置串行收集器,年轻带收集器
+
+-XX:+UseParNewGC:设置年轻代为并行收集。可与 CMS 收集同时使用。JDK5.0 以上,JVM 会根据系统配置自行设置,所以无需再设置此值。
+
+-XX:+UseParallelGC:设置并行收集器,目标是目标是达到可控制的吞吐量
+
+-XX:+UseParallelOldGC:设置并行年老代收集器,JDK6.0 支持对年老代并行收集。
+
+-XX:+UseConcMarkSweepGC:设置年老代并发收集器
+
+-XX:+UseG1GC:设置 G1 收集器,JDK1.9默认垃圾收集器
+
+作者:小杰要吃蛋
+链接:https://juejin.cn/post/6844904125696573448
+来源:稀土掘金
+著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
\ No newline at end of file
diff --git "a/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\351\233\206\345\220\210.md" "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\351\233\206\345\220\210.md"
new file mode 100644
index 0000000..217da0b
--- /dev/null
+++ "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\351\233\206\345\220\210.md"
@@ -0,0 +1,930 @@
+
+假设你是一名资深的 Java 开发工程师,有 5-10 年的大厂开发经验,现在你正在面试,需要你回答下面的一些问题,并且答案需要满足下列要求:
+1、用中文回答;
+2、以 markdown 的格式回答,中英文左右有空格,同时,对你的答案进行重点的突出标注等;
+3、对于特别有需要解释的,难以理解的、有深度的内容,加以代码进行解释;
+4、对相关实现的底层原理进行对比和分析;
+你可以帮助我完成吗?
+
+
+假设你是一名资深的 Java 开发工程师,有 5-10 年的大厂开发经验,现在你是一名面试官,现在你正在面试一名有着 5 年大厂经验的 Java 开发工程师。
+
+
+ConcurrentHashMap 和 Hashtable 的区别是什么?
+需要你详细的回答,对底层的实现原理进行分析。
+然后,用 markdown 格式,重点突出,同时,如果 pdf 的内容有不完善的地方,结合你的理解补充完整。
+
+
+ConcurrentHashMap 和 Hashtable 的区别,需要你详细的回答,对底层的实现原理进行分析。
+然后,用 markdown 格式,重点突出。
+
+
+
+## 集合容器概述
+
+### 什么是集合
+
+简单来说,集合就是一个放数据容器,它主要包括 Collection 和 Map 集合
+
+- 集合只能存放对象,Java中每一种基本数据类型都有对应的引用类型。例如在集合中存储一个int型数据时,要先自动转换成Integer类后再存入;
+- 集合存放的是对对象的引用,对象本身还是存放在堆内存中;
+- 集合可以存放不同类型、不限数量的数据类型。
+
+### 集合和数组的区别
+
+* 数组是固定长度的;集合可变长度的。
+
+* 数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。
+
+* 数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。
+
+
+### 常用的集合类有哪些?
+
+常用的Java集合主要由三大体系:Set、List和Map。其中Set和List是基于Collection接口的实现类,Set中常用的有HashSet和TreeSet,List中常用的有ArrayList,基于Map接口的常用实现类有HashMap和TreeMap。
+
+1. Collection接口的子接口包括:Set接口和List接口
+2. Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
+3. Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
+4. List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
+
+### List,Set,Map三者的区别?
+
+
+
+* Java 容器分为 Collection 和 Map 两大类,Collection集合的子接口有Set、List、Queue三种子接口。我们比较常用的是Set、List,Map接口不是collection的子接口。
+
+* Collection集合主要有List和Set两大接口
+
+ * List:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。
+ * Set:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。
+* Map是一个键值对集合,存储键、值和之间的映射。 Key无序,唯一;value 不要求有序,允许重复。Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。
+
+ * Map 的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap
+
+### 集合框架底层数据结构
+
+* Collection
+
+ 1. List
+
+ * Arraylist: Object数组
+
+ * Vector: Object数组
+
+ * LinkedList: 双向循环链表
+
+ 2. Set
+
+ * HashSet(无序,唯一):基于 HashMap 实现的,底层采用 HashMap 来保存元素
+ * LinkedHashSet: LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。
+ * TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树。)
+* Map
+
+ * HashMap: JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间
+ * LinkedHashMap:LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。
+ * HashTable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
+ * TreeMap: 红黑树(自平衡的排序二叉树)
+
+### 哪些集合类是线程安全的?
+
+* Vector:就比Arraylist多了个 synchronized (线程安全),因为效率较低,现在已经不太建议使用。
+* hashTable:就比hashMap多了个synchronized (线程安全),不建议使用。
+* ConcurrentHashMap:是Java5中支持高并发、高吞吐量的线程安全HashMap实现。它由Segment数组结构和HashEntry数组结构组成。Segment数组在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键-值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构;一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素;每个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。(推荐使用)
+* ...
+
+### Java集合的快速失败机制 “fail-fast”?
+
+* 是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生 fail-fast 机制。
+
+* 例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。
+
+* 原因:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
+
+* 解决办法:
+
+ 1. 在遍历过程中,所有涉及到改变modCount值得地方全部加上synchronized。
+
+ 2. 使用CopyOnWriteArrayList来替换ArrayList
+
+### 怎么确保一个集合不能被修改?
+
+* 可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出 Java. lang. UnsupportedOperationException 异常。
+
+* 示例代码如下:
+
+ List list = new ArrayList<>();
+ list. add("x");
+ Collection clist = Collections. unmodifiableCollection(list);
+ clist. add("y"); // 运行时此行报错
+ System. out. println(list. size());
+ 复制代码
+
+## Collection接口
+
+### List接口
+
+#### 迭代器 Iterator 是什么?
+
+* Iterator 接口提供遍历任何 Collection 的接口。我们可以从一个 Collection 中使用迭代器方法来获取迭代器实例。迭代器取代了 Java 集合框架中的 Enumeration,迭代器允许调用者在迭代过程中移除元素。
+* 因为所有Collection接继承了Iterator迭代器
+
+
+#### Iterator 怎么使用?有什么特点?
+
+* Iterator 使用代码如下:
+
+ List list = new ArrayList<>();
+ Iterator it = list. iterator();
+ while(it. hasNext()){
+ String obj = it. next();
+ System. out. println(obj);
+ }
+ 复制代码
+* Iterator 的特点是只能单向遍历,但是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常。
+
+#### 如何边遍历边移除 Collection 中的元素?
+
+* 边遍历边修改 Collection 的唯一正确方式是使用 Iterator.remove() 方法,如下:
+
+ Iterator it = list.iterator();
+ while(it.hasNext()){
+ *// do something*
+ it.remove();
+ }
+ 复制代码
+
+一种最常见的**错误**代码如下:
+
+for(Integer i : list){
+ list.remove(i)
+}
+复制代码
+
+* 运行以上错误代码会报 **ConcurrentModificationException 异常**。这是因为当使用 foreach(for(Integer i : list)) 语句时,会自动生成一个iterator 来遍历该 list,但同时该 list 正在被 Iterator.remove() 修改。Java 一般不允许一个线程在遍历 Collection 时另一个线程修改它。
+
+#### Iterator 和 ListIterator 有什么区别?
+
+* Iterator 可以遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。
+* Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前/后遍历)。
+* ListIterator 实现 Iterator 接口,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。
+
+#### 遍历一个 List 有哪些不同的方式?每种方法的实现原理是什么?Java 中 List 遍历的最佳实践是什么?
+
+* 遍历方式有以下几种:
+
+ 1. for 循环遍历,基于计数器。在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后停止。
+ 2. 迭代器遍历,Iterator。Iterator 是面向对象的一个设计模式,目的是屏蔽不同数据集合的特点,统一遍历集合的接口。Java 在 Collections 中支持了 Iterator 模式。
+ 3. foreach 循环遍历。foreach 内部也是采用了 Iterator 的方式实现,使用时不需要显式声明 Iterator 或计数器。优点是代码简洁,不易出错;缺点是只能做简单的遍历,不能在遍历过程中操作数据集合,例如删除、替换。
+* 最佳实践:Java Collections 框架中提供了一个 RandomAccess 接口,用来标记 List 实现是否支持 Random Access。
+
+ * 如果一个数据集合实现了该接口,就意味着它支持 Random Access,按位置读取元素的平均时间复杂度为 O(1),如ArrayList。
+
+ * 如果没有实现该接口,表示不支持 Random Access,如LinkedList。
+
+ * 推荐的做法就是,支持 Random Access 的列表可用 for 循环遍历,否则建议用 Iterator 或 foreach 遍历。
+
+#### 说一下 ArrayList 的优缺点
+
+* ArrayList的优点如下:
+
+ * ArrayList 底层以数组实现,是一种随机访问模式。ArrayList 实现了 RandomAccess 接口,因此查找的时候非常快。
+ * ArrayList 在顺序添加一个元素的时候非常方便。
+* ArrayList 的缺点如下:
+
+ * 删除元素的时候,需要做一次元素复制操作。如果要复制的元素很多,那么就会比较耗费性能。
+ * 插入元素的时候,也需要做一次元素复制操作,缺点同上。
+* ArrayList 比较适合顺序添加、随机访问的场景。
+
+#### 如何实现数组和 List 之间的转换?
+
+* 数组转 List:使用 Arrays. asList(array) 进行转换。
+* List 转数组:使用 List 自带的 toArray() 方法。
+
+* 代码示例:
+
+ // list to array
+ List list = new ArrayList();
+ list.add("123");
+ list.add("456");
+ list.toArray();
+
+ // array to list
+ String[] array = new String[]{"123","456"};
+ Arrays.asList(array);
+ 复制代码
+
+#### ArrayList 和 LinkedList 的区别是什么?
+
+* 数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。
+* 随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。
+* 增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。
+* 内存空间占用:LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。
+* 线程安全:ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
+
+* 综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。
+
+* LinkedList 的双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。
+
+#### ArrayList 和 Vector 的区别是什么?
+
+* 这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合
+
+ * 线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的。
+ * 性能:ArrayList 在性能方面要优于 Vector。
+ * 扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%。
+* Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间。
+
+* Arraylist不是同步的,所以在不需要保证线程安全时时建议使用Arraylist。
+
+#### 插入数据时,ArrayList、LinkedList、Vector谁速度较快?阐述 ArrayList、Vector、LinkedList 的存储性能和特性?
+
+* ArrayList和Vector 底层的实现都是使用数组方式存储数据。数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。
+
+* Vector 中的方法由于加了 synchronized 修饰,因此 **Vector** **是线程安全容器,但性能上较ArrayList差**。
+
+* LinkedList 使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但插入数据时只需要记录当前项的前后项即可,所以 **LinkedList** **插入速度较快**。
+
+#### 多线程场景下如何使用 ArrayList?
+
+* ArrayList 不是线程安全的,如果遇到多线程场景,可以通过 Collections 的 synchronizedList 方法将其转换成线程安全的容器后再使用。例如像下面这样:
+
+ List synchronizedList = Collections.synchronizedList(list);
+ synchronizedList.add("aaa");
+ synchronizedList.add("bbb");
+
+ for (int i = 0; i < synchronizedList.size(); i++) {
+ System.out.println(synchronizedList.get(i));
+ }
+ 复制代码
+
+#### 为什么 ArrayList 的 elementData 加上 transient 修饰?
+
+* ArrayList 中的数组定义如下:
+
+ private transient Object[] elementData;
+
+* 再看一下 ArrayList 的定义:
+
+ public class ArrayList extends AbstractList
+ implements List, RandomAccess, Cloneable, java.io.Serializable
+ 复制代码
+* 可以看到 ArrayList 实现了 Serializable 接口,这意味着 ArrayList 支持序列化。transient 的作用是说不希望 elementData 数组被序列化,重写了 writeObject 实现:
+
+ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
+ *// Write out element count, and any hidden stuff*
+ int expectedModCount = modCount;
+ s.defaultWriteObject();
+ *// Write out array length*
+ s.writeInt(elementData.length);
+ *// Write out all elements in the proper order.*
+ for (int i=0; i
+* 每次序列化时,先调用 defaultWriteObject() 方法序列化 ArrayList 中的非 transient 元素,然后遍历 elementData,只序列化已存入的元素,这样既加快了序列化的速度,又减小了序列化之后的文件大小。
+
+#### List 和 Set 的区别
+
+* List , Set 都是继承自Collection 接口
+
+* List 特点:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。
+
+* Set 特点:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。
+
+* 另外 List 支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。
+
+* Set和List对比
+
+ * Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
+ * List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变
+
+### Set接口
+
+#### 说一下 HashSet 的实现原理?
+
+* HashSet 是基于 HashMap 实现的,HashSet的值存放于HashMap的key上,HashMap的value统一为present,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值。
+
+#### HashSet如何检查重复?HashSet是如何保证数据不可重复的?
+
+* 向HashSet 中add ()元素时,判断元素是否存在的依据,不仅要比较hash值,同时还要结合equles 方法比较。
+
+* HashSet 中的add ()方法会使用HashMap 的put()方法。
+
+* HashMap 的 key 是唯一的,由源码可以看出 HashSet 添加进去的值就是作为HashMap 的key,并且在HashMap中如果K/V相同时,会用新的V覆盖掉旧的V,然后返回旧的V。所以不会重复( HashMap 比较key是否相等是先比较hashcode 再比较equals )。
+
+* 以下是HashSet 部分源码:
+
+ private static final Object PRESENT = new Object();
+ private transient HashMap map;
+
+ public HashSet() {
+ map = new HashMap<>();
+ }
+
+ public boolean add(E e) {
+ // 调用HashMap的put方法,PRESENT是一个至始至终都相同的虚值
+ return map.put(e, PRESENT)==null;
+ }
+ 复制代码
+
+**hashCode()与equals()的相关规定**:
+
+1. 如果两个对象相等,则hashcode一定也是相同的
+ * hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值
+2. 两个对象相等,对两个equals方法返回true
+3. 两个对象有相同的hashcode值,它们也不一定是相等的
+4. 综上,equals方法被覆盖过,则hashCode方法也必须被覆盖
+5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。
+
+**==与equals的区别**
+
+1. ==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存空间的值是不是相同
+2. ==是指对内存地址进行比较 equals()是对字符串的内容进行比较
+
+#### HashSet与HashMap的区别
+
+> | HashMap | HashSet |
+> | --- | --- |
+> | 实现了Map接口 | 实现Set接口 |
+> | 存储键值对 | 仅存储对象 |
+> | 调用put()向map中添加元素 | 调用add()方法向Set中添加元素 |
+> | HashMap使用键(Key)计算Hashcode | HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,如果两个对象不同的话,那么返回false |
+> | HashMap相对于HashSet较快,因为它是使用唯一的键获取对象 | HashSet较HashMap来说比较慢 |
+
+## Map接口
+
+### 什么是Hash算法
+
+* 哈希算法是指把任意长度的二进制映射为固定长度的较小的二进制值,这个较小的二进制值叫做哈希值。
+
+### 什么是链表
+
+* 链表是可以将物理地址上不连续的数据连接起来,通过指针来对物理地址进行操作,实现增删改查等功能。
+
+* 链表大致分为单链表和双向链表
+
+ 1. 单链表:每个节点包含两部分,一部分存放数据变量的data,另一部分是指向下一节点的next指针
+
+ 
+ 2. 双向链表:除了包含单链表的部分,还增加的pre前一个节点的指针
+
+ 
+* 链表的优点
+
+ * 插入删除速度快(因为有next指针指向其下一个节点,通过改变指针的指向可以方便的增加删除元素)
+ * 内存利用率高,不会浪费内存(可以使用内存中细小的不连续空间(大于node节点的大小),并且在需要空间的时候才创建空间)
+ * 大小没有固定,拓展很灵活。
+* 链表的缺点
+
+ * 不能随机查找,必须从第一个开始遍历,查找效率低
+
+### 说一下HashMap的实现原理?
+
+* HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
+
+* HashMap的数据结构: 在Java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。
+
+* HashMap 基于 Hash 算法实现的
+
+ 1. 当我们往HashMap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标
+
+ 2. 存储时,如果出现hash值相同的key,此时有两种情况。
+
+ (1)如果key相同,则覆盖原始值;
+
+ (2)如果key不同(出现冲突),则将当前的key-value放入链表中
+
+ 3. 获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。
+
+ 4. 理解了以上过程就不难明白HashMap是如何解决hash冲突的问题,核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。
+
+* 需要注意Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn)
+
+### HashMap在JDK1.7和JDK1.8中有哪些不同?HashMap的底层实现
+
+* 在Java中,保存数据有两种比较简单的数据结构:数组和链表。**数组的特点是:寻址容易,插入和删除困难;链表的特点是:寻址困难,但插入和删除容易;所以我们将数组和链表结合在一起,发挥两者各自的优势,使用一种叫做拉链法**的方式可以解决哈希冲突。
+
+#### HashMap JDK1.8之前
+
+* JDK1.8之前采用的是拉链法。**拉链法**:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
+
+
+#### HashMap JDK1.8之后
+
+* 相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。
+
+
+#### JDK1.7 VS JDK1.8 比较
+
+* JDK1.8主要解决或优化了一下问题:
+ 1. resize 扩容优化
+ 2. 引入了红黑树,目的是避免单条链表过长而影响查询效率,红黑树算法请参考
+ 3. 解决了多线程死循环问题,但仍是非线程安全的,多线程时可能会造成数据丢失问题。
+
+> | 不同 | JDK 1.7 | JDK 1.8 |
+> | --- | --- | --- |
+> | 存储结构 | 数组 + 链表 | 数组 + 链表 + 红黑树 |
+> | 初始化方式 | 单独函数:`inflateTable()` | 直接集成到了扩容函数`resize()`中 |
+> | hash值计算方式 | 扰动处理 = 9次扰动 = 4次位运算 + 5次异或运算 | 扰动处理 = 2次扰动 = 1次位运算 + 1次异或运算 |
+> | 存放数据的规则 | 无冲突时,存放数组;冲突时,存放链表 | 无冲突时,存放数组;冲突 & 链表长度 < 8:存放单链表;冲突 & 链表长度 > 8:树化并存放红黑树 |
+> | 插入数据方式 | 头插法(先讲原位置的数据移到后1位,再插入数据到该位置) | 尾插法(直接插入到链表尾部/红黑树) |
+> | 扩容后存储位置的计算方式 | 全部按照原来方法进行计算(即hashCode ->> 扰动函数 ->> (h&length-1)) | 按照扩容后的规律计算(即扩容后的位置=原位置 or 原位置 + 旧容量) |
+
+### 什么是红黑树
+
+#### 说道红黑树先讲什么是二叉树
+
+* 二叉树简单来说就是 每一个节上可以关联俩个子节点
+
+ * 大概就是这样子:
+ a
+ / \
+ b c
+ / \ / \
+ d e f g
+ / \ / \ / \ / \
+ h i j k l m n o
+ 复制代码
+
+#### 红黑树
+
+* 红黑树是一种特殊的二叉查找树。红黑树的每个结点上都有存储位表示结点的颜色,可以是红(Red)或黑(Black)。 
+
+* 红黑树的每个结点是黑色或者红色。当是不管怎么样他的根结点是黑色。每个叶子结点(叶子结点代表终结、结尾的节点)也是黑色 [注意:这里叶子结点,是指为空(NIL或NULL)的叶子结点!]。
+
+* 如果一个结点是红色的,则它的子结点必须是黑色的。
+
+* 每个结点到叶子结点NIL所经过的黑色结点的个数一样的。[确保没有一条路径会比其他路径长出俩倍,所以红黑树是相对接近平衡的二叉树的!]
+
+* 红黑树的基本操作是**添加、删除**。在对红黑树进行添加或删除之后,都会用到旋转方法。为什么呢?道理很简单,添加或删除红黑树中的结点之后,红黑树的结构就发生了变化,可能不满足上面三条性质,也就不再是一颗红黑树了,而是一颗普通的树。而通过旋转和变色,可以使这颗树重新成为红黑树。简单点说,旋转和变色的目的是让树保持红黑树的特性。
+
+### HashMap的put方法的具体流程?
+
+* 当我们put的时候,首先计算 `key`的`hash`值,这里调用了 `hash`方法,`hash`方法实际是让`key.hashCode()`与`key.hashCode()>>>16`进行异或操作,高16bit补0,一个数和0异或不变,所以 hash 函数大概的作用就是:**高16bit不变,低16bit和高16bit做了一个异或,目的是减少碰撞**。按照函数注释,因为bucket数组大小是2的幂,计算下标`index = (table.length - 1) & hash`,如果不做 hash 处理,相当于散列生效的只有几个低 bit 位,为了减少散列的碰撞,设计者综合考虑了速度、作用、质量之后,使用高16bit和低16bit异或来简单处理减少碰撞,而且JDK8中用了复杂度 O(logn)的树结构来提升碰撞下的性能。
+
+* putVal方法执行流程图
+
+public V put(K key, V value) {
+ return putVal(hash(key), key, value, false, true);
+}
+
+static final int hash(Object key) {
+ int h;
+ return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
+}
+
+//实现Map.put和相关方法
+final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
+ boolean evict) {
+ Node[] tab; Node p; int n, i;
+ // 步骤①:tab为空则创建
+ // table未初始化或者长度为0,进行扩容
+ if ((tab = table) == null || (n = tab.length) == 0)
+ n = (tab = resize()).length;
+ // 步骤②:计算index,并对null做处理
+ // (n - 1) & hash 确定元素存放在哪个桶中,桶为空,新生成结点放入桶中(此时,这个结点是放在数组中)
+ if ((p = tab[i = (n - 1) & hash]) == null)
+ tab[i] = newNode(hash, key, value, null);
+ // 桶中已经存在元素
+ else {
+ Node e; K k;
+ // 步骤③:节点key存在,直接覆盖value
+ // 比较桶中第一个元素(数组中的结点)的hash值相等,key相等
+ if (p.hash == hash &&
+ ((k = p.key) == key || (key != null && key.equals(k))))
+ // 将第一个元素赋值给e,用e来记录
+ e = p;
+ // 步骤④:判断该链为红黑树
+ // hash值不相等,即key不相等;为红黑树结点
+ // 如果当前元素类型为TreeNode,表示为红黑树,putTreeVal返回待存放的node, e可能为null
+ else if (p instanceof TreeNode)
+ // 放入树中
+ e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
+ // 步骤⑤:该链为链表
+ // 为链表结点
+ else {
+ // 在链表最末插入结点
+ for (int binCount = 0; ; ++binCount) {
+ // 到达链表的尾部
+
+ //判断该链表尾部指针是不是空的
+ if ((e = p.next) == null) {
+ // 在尾部插入新结点
+ p.next = newNode(hash, key, value, null);
+ //判断链表的长度是否达到转化红黑树的临界值,临界值为8
+ if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
+ //链表结构转树形结构
+ treeifyBin(tab, hash);
+ // 跳出循环
+ break;
+ }
+ // 判断链表中结点的key值与插入的元素的key值是否相等
+ if (e.hash == hash &&
+ ((k = e.key) == key || (key != null && key.equals(k))))
+ // 相等,跳出循环
+ break;
+ // 用于遍历桶中的链表,与前面的e = p.next组合,可以遍历链表
+ p = e;
+ }
+ }
+ //判断当前的key已经存在的情况下,再来一个相同的hash值、key值时,返回新来的value这个值
+ if (e != null) {
+ // 记录e的value
+ V oldValue = e.value;
+ // onlyIfAbsent为false或者旧值为null
+ if (!onlyIfAbsent || oldValue == null)
+ //用新值替换旧值
+ e.value = value;
+ // 访问后回调
+ afterNodeAccess(e);
+ // 返回旧值
+ return oldValue;
+ }
+ }
+ // 结构性修改
+ ++modCount;
+ // 步骤⑥:超过最大容量就扩容
+ // 实际大小大于阈值则扩容
+ if (++size > threshold)
+ resize();
+ // 插入后回调
+ afterNodeInsertion(evict);
+ return null;
+}
+复制代码
+
+1. 判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;
+2. 根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;
+3. 判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;
+4. 判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向5;
+5. 遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;
+6. 插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。
+
+### HashMap的扩容操作是怎么实现的?
+
+1. 在jdk1.8中,resize方法是在hashmap中的键值对大于阀值时或者初始化时,就调用resize方法进行扩容;
+
+2. 每次扩展的时候,都是扩展2倍;
+
+3. 扩展后Node对象的位置要么在原位置,要么移动到原偏移量两倍的位置。
+
+* 在putVal()中,我们看到在这个函数里面使用到了2次resize()方法,resize()方法表示的在进行第一次初始化时会对其进行扩容,或者当该数组的实际大小大于其临界值值(第一次为12),这个时候在扩容的同时也会伴随的桶上面的元素进行重新分发,这也是JDK1.8版本的一个优化的地方,在1.7中,扩容之后需要重新去计算其Hash值,根据Hash值对其进行分发,但在1.8版本中,则是根据在同一个桶的位置中进行判断(e.hash & oldCap)是否为0,重新进行hash分配后,该元素的位置要么停留在原始位置,要么移动到原始位置+增加的数组大小这个位置上
+
+ final Node[] resize() {
+ Node[] oldTab = table;//oldTab指向hash桶数组
+ int oldCap = (oldTab == null) ? 0 : oldTab.length;
+ int oldThr = threshold;
+ int newCap, newThr = 0;
+ if (oldCap > 0) {//如果oldCap不为空的话,就是hash桶数组不为空
+ if (oldCap >= MAXIMUM_CAPACITY) {//如果大于最大容量了,就赋值为整数最大的阀值
+ threshold = Integer.MAX_VALUE;
+ return oldTab;//返回
+ }//如果当前hash桶数组的长度在扩容后仍然小于最大容量 并且oldCap大于默认值16
+ else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
+ oldCap >= DEFAULT_INITIAL_CAPACITY)
+ newThr = oldThr << 1; // double threshold 双倍扩容阀值threshold
+ }
+ // 旧的容量为0,但threshold大于零,代表有参构造有cap传入,threshold已经被初始化成最小2的n次幂
+ // 直接将该值赋给新的容量
+ else if (oldThr > 0) // initial capacity was placed in threshold
+ newCap = oldThr;
+ // 无参构造创建的map,给出默认容量和threshold 16, 16*0.75
+ else { // zero initial threshold signifies using defaults
+ newCap = DEFAULT_INITIAL_CAPACITY;
+ newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
+ }
+ // 新的threshold = 新的cap * 0.75
+ if (newThr == 0) {
+ float ft = (float)newCap * loadFactor;
+ newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
+ (int)ft : Integer.MAX_VALUE);
+ }
+ threshold = newThr;
+ // 计算出新的数组长度后赋给当前成员变量table
+ @SuppressWarnings({"rawtypes","unchecked"})
+ Node[] newTab = (Node[])new Node[newCap];//新建hash桶数组
+ table = newTab;//将新数组的值复制给旧的hash桶数组
+ // 如果原先的数组没有初始化,那么resize的初始化工作到此结束,否则进入扩容元素重排逻辑,使其均匀的分散
+ if (oldTab != null) {
+ // 遍历新数组的所有桶下标
+ for (int j = 0; j < oldCap; ++j) {
+ Node e;
+ if ((e = oldTab[j]) != null) {
+ // 旧数组的桶下标赋给临时变量e,并且解除旧数组中的引用,否则就数组无法被GC回收
+ oldTab[j] = null;
+ // 如果e.next==null,代表桶中就一个元素,不存在链表或者红黑树
+ if (e.next == null)
+ // 用同样的hash映射算法把该元素加入新的数组
+ newTab[e.hash & (newCap - 1)] = e;
+ // 如果e是TreeNode并且e.next!=null,那么处理树中元素的重排
+ else if (e instanceof TreeNode)
+ ((TreeNode)e).split(this, newTab, j, oldCap);
+ // e是链表的头并且e.next!=null,那么处理链表中元素重排
+ else { // preserve order
+ // loHead,loTail 代表扩容后不用变换下标,见注1
+ Node loHead = null, loTail = null;
+ // hiHead,hiTail 代表扩容后变换下标,见注1
+ Node hiHead = null, hiTail = null;
+ Node next;
+ // 遍历链表
+ do {
+ next = e.next;
+ if ((e.hash & oldCap) == 0) {
+ if (loTail == null)
+ // 初始化head指向链表当前元素e,e不一定是链表的第一个元素,初始化后loHead
+ // 代表下标保持不变的链表的头元素
+ loHead = e;
+ else
+ // loTail.next指向当前e
+ loTail.next = e;
+ // loTail指向当前的元素e
+ // 初始化后,loTail和loHead指向相同的内存,所以当loTail.next指向下一个元素时,
+ // 底层数组中的元素的next引用也相应发生变化,造成lowHead.next.next.....
+ // 跟随loTail同步,使得lowHead可以链接到所有属于该链表的元素。
+ loTail = e;
+ }
+ else {
+ if (hiTail == null)
+ // 初始化head指向链表当前元素e, 初始化后hiHead代表下标更改的链表头元素
+ hiHead = e;
+ else
+ hiTail.next = e;
+ hiTail = e;
+ }
+ } while ((e = next) != null);
+ // 遍历结束, 将tail指向null,并把链表头放入新数组的相应下标,形成新的映射。
+ if (loTail != null) {
+ loTail.next = null;
+ newTab[j] = loHead;
+ }
+ if (hiTail != null) {
+ hiTail.next = null;
+ newTab[j + oldCap] = hiHead;
+ }
+ }
+ }
+ }
+ }
+ return newTab;
+ }
+ 复制代码
+
+### HashMap是怎么解决哈希冲突的?
+
+* 答:在解决这个问题之前,我们首先需要知道**什么是哈希冲突**,而在了解哈希冲突之前我们还要知道**什么是哈希**才行;
+
+#### 什么是哈希?
+
+* Hash,一般翻译为“散列”,也有直接音译为“哈希”的, Hash就是指使用哈希算法是指把任意长度的二进制映射为固定长度的较小的二进制值,这个较小的二进制值叫做哈希值。
+
+#### 什么是哈希冲突?
+
+* **当两个不同的输入值,根据同一散列函数计算出相同的散列值的现象,我们就把它叫做碰撞(哈希碰撞)**。
+
+#### HashMap的数据结构
+
+* 在Java中,保存数据有两种比较简单的数据结构:数组和链表。
+ * 数组的特点是:寻址容易,插入和删除困难;
+ * 链表的特点是:寻址困难,但插入和删除容易;
+* 所以我们将数组和链表结合在一起,发挥两者各自的优势,就可以使用俩种方式:链地址法和开放地址法可以解决哈希冲突:
+
+
+
+* 链表法就是将相同hash值的对象组织成一个链表放在hash值对应的槽位;
+* 开放地址法是通过一个探测算法,当某个槽位已经被占据的情况下继续查找下一个可以使用的槽位。
+* **但相比于hashCode返回的int类型,我们HashMap初始的容量大小`DEFAULT_INITIAL_CAPACITY = 1 << 4`(即2的四次方16)要远小于int类型的范围,所以我们如果只是单纯的用hashCode取余来获取对应的bucket这将会大大增加哈希碰撞的概率,并且最坏情况下还会将HashMap变成一个单链表**,所以我们还需要对hashCode作一定的优化
+
+#### hash()函数
+
+* 上面提到的问题,主要是因为如果使用hashCode取余,那么相当于**参与运算的只有hashCode的低位**,高位是没有起到任何作用的,所以我们的思路就是让hashCode取值出的高位也参与运算,进一步降低hash碰撞的概率,使得数据分布更平均,我们把这样的操作称为**扰动**,在**JDK 1.8**中的hash()函数如下:
+
+ static final int hash(Object key) {
+ int h;
+ return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);// 与自己右移16位进行异或运算(高低位异或)
+ }
+ 复制代码
+* 这比在**JDK 1.7**中,更为简洁,**相比在1.7中的4次位运算,5次异或运算(9次扰动),在1.8中,只进行了1次位运算和1次异或运算(2次扰动)**;
+
+#### 总结
+
+* 简单总结一下HashMap是使用了哪些方法来有效解决哈希冲突的:
+ * 链表法就是将相同hash值的对象组织成一个链表放在hash值对应的槽位;
+ * 开放地址法是通过一个探测算法,当某个槽位已经被占据的情况下继续查找下一个可以使用的槽位。
+
+### 能否使用任何类作为 Map 的 key?
+
+可以使用任何类作为 Map 的 key,然而在使用之前,需要考虑以下几点:
+
+* 如果类重写了 equals() 方法,也应该重写 hashCode() 方法。
+
+* 类的所有实例需要遵循与 equals() 和 hashCode() 相关的规则。
+
+* 如果一个类没有使用 equals(),不应该在 hashCode() 中使用它。
+
+* 用户自定义 Key 类最佳实践是使之为不可变的,这样 hashCode() 值可以被缓存起来,拥有更好的性能。不可变的类也可以确保 hashCode() 和 equals() 在未来不会改变,这样就会解决与可变相关的问题了。
+
+### 为什么HashMap中String、Integer这样的包装类适合作为K?
+
+* 答:String、Integer等包装类的特性能够保证Hash值的不可更改性和计算准确性,能够有效的减少Hash碰撞的几率
+ * 都是final类型,即不可变性,保证key的不可更改性,不会存在获取hash值不同的情况
+ * 内部已重写了`equals()`、`hashCode()`等方法,遵守了HashMap内部的规范(不清楚可以去上面看看putValue的过程),不容易出现Hash值计算错误的情况;
+
+### 如果使用Object作为HashMap的Key,应该怎么办呢?
+
+* 答:重写`hashCode()`和`equals()`方法
+ 1. **重写`hashCode()`是因为需要计算存储数据的存储位置**,需要注意不要试图从散列码计算中排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的Hash碰撞;
+ 2. **重写`equals()`方法**,需要遵守自反性、对称性、传递性、一致性以及对于任何非null的引用值x,x.equals(null)必须返回false的这几个特性,**目的是为了保证key在哈希表中的唯一性**;
+
+### HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标?
+
+* 答:`hashCode()`方法返回的是int整数类型,其范围为-(2 ^ 31)~(2 ^ 31 - 1),约有40亿个映射空间,而HashMap的容量范围是在16(初始化默认值)~2 ^ 30,HashMap通常情况下是取不到最大值的,并且设备上也难以提供这么多的存储空间,从而导致通过`hashCode()`计算出的哈希值可能不在数组大小范围内,进而无法匹配存储位置;
+
+* **那怎么解决呢?**
+
+ 1. HashMap自己实现了自己的`hash()`方法,通过两次扰动使得它自己的哈希值高低位自行进行异或运算,降低哈希碰撞概率也使得数据分布更平均;
+
+ 2. 在保证数组长度为2的幂次方的时候,使用`hash()`运算之后的值与运算(&)(数组长度 - 1)来获取数组下标的方式进行存储,这样一来是比取余操作更加有效率,二来也是因为只有当数组长度为2的幂次方时,h&(length-1)才等价于h%length,三来解决了“哈希值与数组大小范围不匹配”的问题;
+
+### HashMap 的长度为什么是2的幂次方
+
+* 为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀,每个链表/红黑树长度大致相同。这个实现就是把数据存到哪个链表/红黑树中的算法。
+
+* **这个算法应该如何设计呢?**
+
+ * 我们首先可能会想到采用%取余的操作来实现。但是,重点来了:“取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。” 并且 采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是2的幂次方。
+* **那为什么是两次扰动呢?**
+
+ * 答:这样就是加大哈希值低位的随机性,使得分布更均匀,从而提高对应数组存储下标位置的随机性&均匀性,最终减少Hash冲突,两次就够了,已经达到了高位低位同时参与运算的目的;
+
+### HashMap 与 HashTable 有什么区别?
+
+1. **线程安全**: HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过 `synchronized` 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap );
+2. **效率**: 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它;(如果你要保证线程安全的话就使用 ConcurrentHashMap );
+3. **对Null key 和Null value的支持**: HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛NullPointerException。
+4. **初始容量大小和每次扩充容量大小的不同** :
+ 1. 创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。
+ 2. 创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。
+5. **底层数据结构**: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。
+6. 推荐使用:在 Hashtable 的类注释可以看到,Hashtable 是保留类不建议使用,推荐在单线程环境下使用 HashMap 替代,如果需要多线程使用则用 ConcurrentHashMap 替代。
+
+### 什么是TreeMap 简介
+
+* TreeMap 是一个**有序的key-value集合**,它是通过红黑树实现的。
+* TreeMap基于**红黑树(Red-Black tree)实现**。该映射根据**其键的自然顺序进行排序**,或者根据**创建映射时提供的 Comparator 进行排序**,具体取决于使用的构造方法。
+* TreeMap是线程**非同步**的。
+
+### 如何决定使用 HashMap 还是 TreeMap?
+
+* 对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。
+
+### HashMap 和 ConcurrentHashMap 的区别
+
+1. ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用lock锁进行保护,相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。(JDK1.8之后ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。)
+2. HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。
+
+### ConcurrentHashMap 和 Hashtable 的区别?
+
+* ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。
+
+ * **底层数据结构**: JDK1.7的 ConcurrentHashMap 底层采用 **分段的数组+链表** 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 **数组+链表** 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
+ * **实现线程安全的方式**:
+ 1. **在JDK1.7的时候,ConcurrentHashMap(分段锁)** 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment,比Hashtable效率提高16倍。) **到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化)** 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;
+ 2. ② **Hashtable(同一把锁)** :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。
+* **两者的对比图**:
+
+##### 1、HashTable:
+
+
+##### 2、 JDK1.7的ConcurrentHashMap:
+
+
+##### 3、JDK1.8的ConcurrentHashMap(TreeBin: 红黑二叉树节点 Node: 链表节点):
+
+
+
+* 答:ConcurrentHashMap 结合了 HashMap 和 HashTable 二者的优势。HashMap 没有考虑同步,HashTable 考虑了同步的问题使用了synchronized 关键字,所以 HashTable 在每次同步执行时都要锁住整个结构。 ConcurrentHashMap 锁的方式是稍微细粒度的。
+
+### ConcurrentHashMap 底层具体实现知道吗?实现原理是什么?
+
+#### JDK1.7
+
+* 首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
+
+* 在JDK1.7中,ConcurrentHashMap采用Segment + HashEntry的方式进行实现,结构如下:
+
+* 一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。
+
+
+
+1. 该类包含两个静态内部类 HashEntry 和 Segment ;前者用来封装映射表的键值对,后者用来充当锁的角色;
+2. Segment 是一种可重入的锁 ReentrantLock,每个 Segment 守护一个HashEntry 数组里得元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 锁。
+
+#### JDK1.8
+
+* 在**JDK1.8中,放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现**,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。
+
+* 结构如下:
+
+
+
+* **附加源码,有需要的可以看看**
+
+* 插入元素过程(建议去看看源码):
+
+* 如果相应位置的Node还没有初始化,则调用CAS插入相应的数据;
+
+ else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
+ if (casTabAt(tab, i, null, new Node(hash, key, value, null)))
+ break; // no lock when adding to empty bin
+ }
+ 复制代码
+* 如果相应位置的Node不为空,且当前该节点不处于移动状态,则对该节点加synchronized锁,如果该节点的hash不小于0,则遍历链表更新节点或插入新节点;
+
+ if (fh >= 0) {
+ binCount = 1;
+ for (Node e = f;; ++binCount) {
+ K ek;
+ if (e.hash == hash &&
+ ((ek = e.key) == key ||
+ (ek != null && key.equals(ek)))) {
+ oldVal = e.val;
+ if (!onlyIfAbsent)
+ e.val = value;
+ break;
+ }
+ Node pred = e;
+ if ((e = e.next) == null) {
+ pred.next = new Node(hash, key, value, null);
+ break;
+ }
+ }
+ }
+ 复制代码
+
+1. 如果该节点是TreeBin类型的节点,说明是红黑树结构,则通过putTreeVal方法往红黑树中插入节点;如果binCount不为0,说明put操作对数据产生了影响,如果当前链表的个数达到8个,则通过treeifyBin方法转化为红黑树,如果oldVal不为空,说明是一次更新操作,没有对元素个数产生影响,则直接返回旧值;
+2. 如果插入的是一个新节点,则执行addCount()方法尝试更新元素个数baseCount;
+
+## 辅助工具类
+
+### Array 和 ArrayList 有何区别?
+
+* Array 可以存储基本数据类型和对象,ArrayList 只能存储对象。
+* Array 是指定固定大小的,而 ArrayList 大小是自动扩展的。
+* Array 内置方法没有 ArrayList 多,比如 addAll、removeAll、iteration 等方法只有 ArrayList 有。
+
+`对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。`
+
+### 如何实现 Array 和 List 之间的转换?
+
+* Array 转 List: Arrays. asList(array) ;
+* List 转 Array:List 的 toArray() 方法。
+
+### comparable 和 comparator的区别?
+
+* comparable接口实际上是出自java.lang包,它有一个 compareTo(Object obj)方法用来排序
+* comparator接口实际上是出自 java.util 包,它有一个compare(Object obj1, Object obj2)方法用来排序
+
+* 一般我们需要对一个集合使用自定义排序时,我们就要重写compareTo方法或compare方法,当我们需要对某一个集合实现两种排序方式,比如一个song对象中的歌名和歌手名分别采用一种排序方法的话,我们可以重写compareTo方法和使用自制的Comparator方法或者以两个Comparator来实现歌名排序和歌星名排序,第二种代表我们只能使用两个参数版的Collections.sort().
+
+### Collection 和 Collections 有什么区别?
+
+* java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。
+* Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。
+
+### TreeMap 和 TreeSet 在排序时如何比较元素?Collections 工具类中的 sort()方法如何比较元素?
+
+* TreeSet 要求存放的对象所属的类必须实现 Comparable 接口,该接口提供了比较元素的 compareTo()方法,当插入元素时会回调该方法比较元素的大小。TreeMap 要求存放的键值对映射的键必须实现 Comparable 接口从而根据键对元素进 行排 序。
+
+* Collections 工具类的 sort 方法有两种重载的形式,
+
+* 第一种要求传入的待排序容器中存放的对象比较实现 Comparable 接口以实现元素的比较;
+
+?
+
+* comparable接口实际上是出自java.lang包,它有一个 compareTo(Object obj)方法用来排序
+* comparator接口实际上是出自 java.util 包,它有一个compare(Object obj1, Object obj2)方法用来排序
+
+* 一般我们需要对一个集合使用自定义排序时,我们就要重写compareTo方法或compare方法,当我们需要对某一个集合实现两种排序方式,比如一个song对象中的歌名和歌手名分别采用一种排序方法的话,我们可以重写compareTo方法和使用自制的Comparator方法或者以两个Comparator来实现歌名排序和歌星名排序,第二种代表我们只能使用两个参数版的Collections.sort().
+
+### Collection 和 Collections 有什么区别?
+
+* java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。
+* Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。
+
+### TreeMap 和 TreeSet 在排序时如何比较元素?Collections 工具类中的 sort()方法如何比较元素?
+
+* TreeSet 要求存放的对象所属的类必须实现 Comparable 接口,该接口提供了比较元素的 compareTo()方法,当插入元素时会回调该方法比较元素的大小。TreeMap 要求存放的键值对映射的键必须实现 Comparable 接口从而根据键对元素进 行排 序。
+
+* Collections 工具类的 sort 方法有两种重载的形式,
+
+* 第一种要求传入的待排序容器中存放的对象比较实现 Comparable 接口以实现元素的比较;
+
+* 第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator 接口的子类型(需要重写 compare 方法实现元素的比较),相当于一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对回调模式的应用(Java 中对函数式编程的支持)。
+
+作者:小杰要吃蛋
+链接:https://juejin.cn/post/6844904125939843079
+来源:稀土掘金
+著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
\ No newline at end of file
diff --git "a/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Linux.md" "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Linux.md"
new file mode 100644
index 0000000..e799b7b
--- /dev/null
+++ "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Linux.md"
@@ -0,0 +1,676 @@
+
+
+## Linux 概述
+
+### 什么是Linux
+
+* Linux是一套免费使用和自由传播的类似Unix操作系统,一般的WEB项目都是部署都是放在Linux操作系统上面。 Linux是一个基于POSIX和Unix的多用户、多任务、支持多线程和多CPU的操作系统。它能运行主要的Unix工具软件、应用程序和网络协议。它支持32位和64位硬件。Linux继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。
+
+ 
+
+### Windows和Linux的区别
+
+* Windows是微软开发的操作系统,民用操作系统,可用于娱乐、影音、上网。 Windows操作系统具有强大的日志记录系统和强大的桌面应用。好处是它可以帮我们实现非常多绚丽多彩的效果,可以非常方便去进行娱乐、影音、上网。
+* Linux的应用相对单纯很多,没有什么绚丽多彩的效果,因此Linux的性能是非常出色的,可以完全针对机器的配置有针对性的优化,
+* 简单来说Windows适合普通用户进行娱乐办公使用,Linux适合软件开发部署
+
+### Unix和Linux有什么区别?
+
+* Linux和Unix都是功能强大的操作系统,都是应用广泛的服务器操作系统,有很多相似之处,甚至有一部分人错误地认为Unix和Linux操作系统是一样的,然而,事实并非如此,以下是两者的区别。
+ 1. 开源性
+ Linux是一款开源操作系统,不需要付费,即可使用;Unix是一款对源码实行知识产权保护的传统商业软件,使用需要付费授权使用。
+ 2. 跨平台性
+ Linux操作系统具有良好的跨平台性能,可运行在多种硬件平台上;Unix操作系统跨平台性能较弱,大多需与硬件配套使用。
+ 3. 可视化界面
+ Linux除了进行命令行操作,还有窗体管理系统;Unix只是命令行下的系统。
+ 4. 硬件环境
+ Linux操作系统对硬件的要求较低,安装方法更易掌握;Unix对硬件要求比较苛刻,按照难度较大。
+ 5. 用户群体
+ Linux的用户群体很广泛,个人和企业均可使用;Unix的用户群体比较窄,多是安全性要求高的大型企业使用,如银行、电信部门等,或者Unix硬件厂商使用,如Sun等。
+ 相比于Unix操作系统,Linux操作系统更受广大计算机爱好者的喜爱,主要原因是Linux操作系统具有Unix操作系统的全部功能,并且能够在普通PC计算机上实现全部的Unix特性,开源免费的特性,更容易普及使用!
+
+### 什么是 Linux 内核?
+
+* Linux 系统的核心是内核。内核控制着计算机系统上的所有硬件和软件,在必要时分配硬件,并根据需要执行软件。
+ 1. 系统内存管理
+ 2. 应用程序管理
+ 3. 硬件设备管理
+ 4. 文件系统管理
+
+### Linux的基本组件是什么?
+
+* 就像任何其他典型的操作系统一样,Linux拥有所有这些组件:内核,shell和GUI,系统实用程序和应用程序。Linux比其他操作系统更具优势的是每个方面都附带其他功能,所有代码都可以免费下载。
+
+### Linux 的体系结构
+
+* 从大的方面讲,Linux 体系结构可以分为两块:
+
+
+
+* 用户空间(User Space) :用户空间又包括用户的应用程序(User Applications)、C 库(C Library) 。
+* 内核空间(Kernel Space) :内核空间又包括系统调用接口(System Call Interface)、内核(Kernel)、平台架构相关的代码(Architecture-Dependent Kernel Code) 。
+
+**为什么 Linux 体系结构要分为用户空间和内核空间的原因?**
+
+* 1、现代 CPU 实现了不同的工作模式,不同模式下 CPU 可以执行的指令和访问的寄存器不同。
+* 2、Linux 从 CPU 的角度出发,为了保护内核的安全,把系统分成了两部分。
+
+* 用户空间和内核空间是程序执行的**两种不同的状态**,我们可以通过两种方式完成用户空间到内核空间的转移:
+ * 系统调用;
+ * 硬件中断。
+
+### BASH和DOS之间的基本区别是什么?
+
+* BASH和DOS控制台之间的主要区别在于3个方面:
+ * BASH命令区分大小写,而DOS命令则不区分;
+ * 在BASH下,/ character是目录分隔符,\作为转义字符。在DOS下,/用作命令参数分隔符,\是目录分隔符
+ * DOS遵循命名文件中的约定,即8个字符的文件名后跟一个点,扩展名为3个字符。BASH没有遵循这样的惯例。
+
+### Linux 开机启动过程?
+
+> 了解即可。
+
+* 1、主机加电自检,加载 BIOS 硬件信息。
+
+* 2、读取 MBR 的引导文件(GRUB、LILO)。
+
+* 3、引导 Linux 内核。
+
+* 4、运行第一个进程 init (进程号永远为 1 )。
+
+* 5、进入相应的运行级别。
+
+* 6、运行终端,输入用户名和密码。
+
+### Linux系统缺省的运行级别?
+
+* 关机。
+* 单机用户模式。
+* 字符界面的多用户模式(不支持网络)。
+* 字符界面的多用户模式。
+* 未分配使用。
+* 图形界面的多用户模式。
+* 重启。
+
+### Linux 使用的进程间通信方式?
+
+> 了解即可,不需要太深入。
+
+* 1、管道(pipe)、流管道(s_pipe)、有名管道(FIFO)。
+* 2、信号(signal) 。
+* 3、消息队列。
+* 4、共享内存。
+* 5、信号量。
+* 6、套接字(socket) 。
+
+### Linux 有哪些系统日志文件?
+
+* 比较重要的是 `/var/log/messages` 日志文件。
+
+> 该日志文件是许多进程日志文件的汇总,从该文件可以看出任何入侵企图或成功的入侵。
+>
+> 另外,如果胖友的系统里有 ELK 日志集中收集,它也会被收集进去。
+
+### Linux系统安装多个桌面环境有帮助吗?
+
+* 通常,一个桌面环境,如KDE或Gnome,足以在没有问题的情况下运行。尽管系统允许从一个环境切换到另一个环境,但这对用户来说都是优先考虑的问题。有些程序在一个环境中工作而在另一个环境中无法工作,因此它也可以被视为选择使用哪个环境的一个因素。
+
+### 什么是交换空间?
+
+* 交换空间是Linux使用的一定空间,用于临时保存一些并发运行的程序。当RAM没有足够的内存来容纳正在执行的所有程序时,就会发生这种情况。
+
+### 什么是root帐户
+
+* root帐户就像一个系统管理员帐户,允许你完全控制系统。你可以在此处创建和维护用户帐户,为每个帐户分配不同的权限。每次安装Linux时都是默认帐户。
+
+### 什么是LILO?
+
+* LILO是Linux的引导加载程序。它主要用于将Linux操作系统加载到主内存中,以便它可以开始运行。
+
+### 什么是BASH?
+
+* BASH是Bourne Again SHell的缩写。它由Steve Bourne编写,作为原始Bourne Shell(由/ bin / sh表示)的替代品。它结合了原始版本的Bourne Shell的所有功能,以及其他功能,使其更容易使用。从那以后,它已被改编为运行Linux的大多数系统的默认shell。
+
+### 什么是CLI?
+
+* **命令行界面**(英语**:command-line interface**,缩写]**:CLI**)是在图形用户界面得到普及之前使用最为广泛的用户界面,它通常不支持鼠标,用户通过键盘输入指令,计算机接收到指令后,予以执行。也有人称之为**字符用户界面**(CUI)。
+
+* 通常认为,命令行界面(CLI)没有图形用户界面(GUI)那么方便用户操作。因为,命令行界面的软件通常需要用户记忆操作的命令,但是,由于其本身的特点,命令行界面要较图形用户界面节约计算机系统的资源。在熟记命令的前提下,使用命令行界面往往要较使用图形用户界面的操作速度要快。所以,图形用户界面的操作系统中,都保留着可选的命令行界面。
+
+### 什么是GUI?
+
+* 图形用户界面(Graphical User Interface,简称 GUI,又称图形用户接口)是指采用图形方式显示的计算机操作用户界面。
+
+* 图形用户界面是一种人与计算机通信的界面显示格式,允许用户使用鼠标等输入设备操纵屏幕上的图标或菜单选项,以选择命令、调用文件、启动程序或执行其它一些日常任务。与通过键盘输入文本或字符命令来完成例行任务的字符界面相比,图形用户界面有许多优点。
+
+### 开源的优势是什么?
+
+* 开源允许你将软件(包括源代码)免费分发给任何感兴趣的人。然后,人们可以添加功能,甚至可以调试和更正源代码中的错误。它们甚至可以让它运行得更好,然后再次自由地重新分配这些增强的源代码。这最终使社区中的每个人受益。
+
+### GNU项目的重要性是什么?
+
+* 这种所谓的自由软件运动具有多种优势,例如可以自由地运行程序以及根据你的需要自由学习和修改程序。它还允许你将软件副本重新分发给其他人,以及自由改进软件并将其发布给公众。
+
+## 磁盘、目录、文件
+
+### 简单 Linux 文件系统?
+
+**在 Linux 操作系统中,所有被操作系统管理的资源,例如网络接口卡、磁盘驱动器、打印机、输入输出设备、普通文件或是目录都被看作是一个文件。**
+
+* 也就是说在 Linux 系统中有一个重要的概念**:一切都是文件**。其实这是 Unix 哲学的一个体现,而 Linux 是重写 Unix 而来,所以这个概念也就传承了下来。在 Unix 系统中,把一切资源都看作是文件,包括硬件设备。UNIX系统把每个硬件都看成是一个文件,通常称为设备文件,这样用户就可以用读写文件的方式实现对硬件的访问。
+
+* Linux 支持 5 种文件类型,如下图所示:
+
+ 
+
+### Linux 的目录结构是怎样的?
+
+> 这个问题,一般不会问。更多是实际使用时,需要知道。
+
+* Linux 文件系统的结构层次鲜明,就像一棵倒立的树,最顶层是其根目录:
+ **常见目录说明**:
+
+> | 目录 | 介绍 |
+> | --- | --- |
+> | /bin | 存放二进制可执行文件(ls,cat,mkdir等),常用命令一般都在这里; |
+> | /etc | 存放系统管理和配置文件; |
+> | /home | 存放所有用户文件的根目录,是用户主目录的基点,比如用户user的主目录就是/home/user,可以用~user表示; |
+> | /usr | 用于存放系统应用程序; |
+> | /opt | 额外安装的可选应用程序包所放置的位置。一般情况下,我们可以把tomcat等都安装到这里; |
+> | /proc | 虚拟文件系统目录,是系统内存的映射。可直接访问这个目录来获取系统信息; |
+> | /root | 超级用户(系统管理员)的主目录(特权阶级); |
+> | /sbin | 存放二进制可执行文件,只有root才能访问。这里存放的是系统管理员使用的系统级别的管理命令和程序。如ifconfig等; |
+> | /dev | 用于存放设备文件; |
+> | /mnt | 系统管理员安装临时文件系统的安装点,系统提供这个目录是让用户临时挂载其他的文件系统; |
+> | /boot | 存放用于系统引导时使用的各种文件; |
+> | /lib | 存放着和系统运行相关的库文件 ; |
+> | /tmp | 用于存放各种临时文件,是公用的临时文件存储点; |
+> | /var | 用于存放运行时需要改变数据的文件,也是某些大文件的溢出区,比方说各种服务的日志文件(系统启动日志等。)等; |
+> | /lost+found | 这个目录平时是空的,系统非正常关机而留下“无家可归”的文件(windows下叫什么.chk)就在这里 |
+
+### 什么是 inode ?
+
+> 一般来说,面试不会问 inode 。但是 inode 是一个重要概念,是理解 Unix/Linux 文件系统和硬盘储存的基础。
+
+* 理解inode,要从文件储存说起。
+
+* 文件储存在硬盘上,硬盘的最小存储单位叫做"扇区"(Sector)。每个扇区储存512字节(相当于0.5KB)。
+
+* 操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个"块"(block)。这种由多个扇区组成的"块",是文件存取的最小单位。"块"的大小,最常见的是4KB,即连续八个 sector组成一个 block。
+
+* 文件数据都储存在"块"中,那么很显然,我们还必须找到一个地方储存文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做inode,中文译名为"索引节点"。
+
+* 每一个文件都有对应的inode,里面包含了与该文件有关的一些信息。
+
+**简述 Linux 文件系统通过 i 节点把文件的逻辑结构和物理结构转换的工作过程?**
+
+> 如果看的一脸懵逼,也没关系。一般来说,面试官不太会问这个题目。
+
+* Linux 通过 inode 节点表将文件的逻辑结构和物理结构进行转换。
+ * inode 节点是一个 64 字节长的表,表中包含了文件的相关信息,其中有文件的大小、文件所有者、文件的存取许可方式以及文件的类型等重要信息。在 inode 节点表中最重要的内容是磁盘地址表。在磁盘地址表中有 13 个块号,文件将以块号在磁盘地址表中出现的顺序依次读取相应的块。
+ * Linux 文件系统通过把 inode 节点和文件名进行连接,当需要读取该文件时,文件系统在当前目录表中查找该文件名对应的项,由此得到该文件相对应的 inode 节点号,通过该 inode 节点的磁盘地址表把分散存放的文件物理块连接成文件的逻辑结构。
+
+### 什么是硬链接和软链接?
+
+* **硬链接**:由于 Linux 下的文件是通过索引节点(inode)来识别文件,硬链接可以认为是一个指针,指向文件索引节点的指针,系统并不为它重新分配 inode 。每添加一个一个硬链接,文件的链接数就加 1 。
+
+ * 不足:
+ 1. 不可以在不同文件系统的文件间建立链接;
+ 2. 只有超级用户才可以为目录创建硬链接。
+* **软链接**:软链接克服了硬链接的不足,没有任何文件系统的限制,任何用户可以创建指向目录的符号链接。因而现在更为广泛使用,它具有更大的灵活性,甚至可以跨越不同机器、不同网络对文件进行链接。
+
+ * 不足:因为链接文件包含有原文件的路径信息,所以当原文件从一个目录下移到其他目录中,再访问链接文件,系统就找不到了,而硬链接就没有这个缺陷,你想怎么移就怎么移;还有它要系统分配额外的空间用于建立新的索引节点和保存原文件的路径。
+* **实际场景下,基本是使用软链接**。总结区别如下:
+
+ * 硬链接不可以跨分区,软件链可以跨分区。
+ * 硬链接指向一个 inode 节点,而软链接则是创建一个新的 inode 节点。
+ * 删除硬链接文件,不会删除原文件,删除软链接文件,会把原文件删除。
+
+### RAID 是什么?
+
+> RAID 全称为独立磁盘冗余阵列(Redundant Array of Independent Disks),基本思想就是把多个相对便宜的硬盘组合起来,成为一个硬盘阵列组,使性能达到甚至超过一个价格昂贵、 容量巨大的硬盘。RAID 通常被用在服务器电脑上,使用完全相同的硬盘组成一个逻辑扇区,因此操作系统只会把它当做一个硬盘。
+>
+> RAID 分为不同的等级,各个不同的等级均在数据可靠性及读写性能上做了不同的权衡。在实际应用中,可以依据自己的实际需求选择不同的 RAID 方案。
+
+* 当然,因为很多公司都使用云服务,大家很难接触到 RAID 这个概念,更多的可能是普通云盘、SSD 云盘酱紫的概念。
+
+## 安全
+
+### 一台 Linux 系统初始化环境后需要做一些什么安全工作?
+
+* 1、添加普通用户登陆,禁止 root 用户登陆,更改 SSH 端口号。
+
+ > 修改 SSH 端口不一定绝对哈。当然,如果要暴露在外网,建议改下。l
+
+* 2、服务器使用密钥登陆,禁止密码登陆。
+
+* 3、开启防火墙,关闭 SElinux ,根据业务需求设置相应的防火墙规则。
+
+* 4、装 fail2ban 这种防止 SSH 暴力破击的软件。
+
+* 5、设置只允许公司办公网出口 IP 能登陆服务器(看公司实际需要)
+
+ > 也可以安装 VPN 等软件,只允许连接 VPN 到服务器上。
+
+* 6、修改历史命令记录的条数为 10 条。
+
+* 7、只允许有需要的服务器可以访问外网,其它全部禁止。
+
+* 8、做好软件层面的防护。
+
+ * 8.1 设置 nginx_waf 模块防止 SQL 注入。
+ * 8.2 把 Web 服务使用 www 用户启动,更改网站目录的所有者和所属组为 www 。
+
+### 什么叫 CC 攻击?什么叫 DDOS 攻击?
+
+* CC 攻击,主要是用来攻击页面的,模拟多个用户不停的对你的页面进行访问,从而使你的系统资源消耗殆尽。
+
+* DDOS 攻击,中文名叫分布式拒绝服务攻击,指借助服务器技术将多个计算机联合起来作为攻击平台,来对一个或多个目标发动 DDOS 攻击。
+
+ > 攻击,即是通过大量合法的请求占用大量网络资源,以达到瘫痪网络的目的。
+
+**怎么预防 CC 攻击和 DDOS 攻击?**
+
+* 防 CC、DDOS 攻击,这些只能是用硬件防火墙做流量清洗,将攻击流量引入黑洞。
+
+> 流量清洗这一块,主要是买 ISP 服务商的防攻击的服务就可以,机房一般有空余流量,我们一般是买服务,毕竟攻击不会是持续长时间。
+
+### 什么是网站数据库注入?
+
+* 由于程序员的水平及经验参差不齐,大部分程序员在编写代码的时候,没有对用户输入数据的合法性进行判断。
+* 应用程序存在安全隐患。用户可以提交一段数据库查询代码,根据程序返回的结果,获得某些他想得知的数据,这就是所谓的 SQL 注入。
+* SQL注入,是从正常的 WWW 端口访问,而且表面看起来跟一般的 Web 页面访问没什么区别,如果管理员没查看日志的习惯,可能被入侵很长时间都不会发觉。
+
+**如何过滤与预防?**
+
+* 数据库网页端注入这种,可以考虑使用 nginx_waf 做过滤与预防。
+
+### Shell 脚本是什么?
+
+* 一个 Shell 脚本是一个文本文件,包含一个或多个命令。作为系统管理员,我们经常需要使用多个命令来完成一项任务,我们可以添加这些所有命令在一个文本文件(Shell 脚本)来完成这些日常工作任务。
+
+## 实战
+
+### 如何选择 Linux 操作系统版本?
+
+**一般来讲,桌面用户首选 Ubuntu ;服务器首选 RHEL 或 CentOS ,两者中首选 CentOS 。**
+
+* 根据具体要求:
+
+ * 安全性要求较高,则选择 Debian 或者 FreeBSD 。
+
+ * 需要使用数据库高级服务和电子邮件网络应用的用户可以选择 SUSE 。
+
+ * 想要新技术新功能可以选择 Feddora ,Feddora 是 RHEL 和 CentOS 的一个测试版和预发布版本。
+
+ * 【重点】**根据现有状况,绝大多数互联网公司选择 CentOS 。现在比较常用的是 6 系列,现在市场占有大概一半左右。另外的原因是 CentOS 更侧重服务器领域,并且无版权约束**。
+
+ > CentOS 7 系列,也慢慢使用的会比较多了。
+
+### 如何规划一台 Linux 主机,步骤是怎样?
+
+* 1、确定机器是做什么用的,比如是做 WEB 、DB、还是游戏服务器。
+
+ > 不同的用途,机器的配置会有所不同。
+
+* 2、确定好之后,就要定系统需要怎么安装,默认安装哪些系统、分区怎么做。
+
+* 3、需要优化系统的哪些参数,需要创建哪些用户等等的。
+
+### 请问当用户反馈网站访问慢,你会如何处理?
+
+**有哪些方面的因素会导致网站网站访问慢?**
+
+* 1、服务器出口带宽不够用
+
+ > * 本身服务器购买的出口带宽比较小。一旦并发量大的话,就会造成分给每个用户的出口带宽就小,访问速度自然就会慢。
+ > * 跨运营商网络导致带宽缩减。例如,公司网站放在电信的网络上,那么客户这边对接是长城宽带或联通,这也可能导致带宽的缩减。
+
+* 2、服务器负载过大,导致响应不过来
+
+ > 可以从两个方面入手分析:
+ >
+ > * 分析系统负载,使用 w 命令或者 uptime 命令查看系统负载。如果负载很高,则使用 top 命令查看 CPU ,MEM 等占用情况,要么是 CPU 繁忙,要么是内存不够。
+ > * 如果这二者都正常,再去使用 sar 命令分析网卡流量,分析是不是遭到了攻击。一旦分析出问题的原因,采取对应的措施解决,如决定要不要杀死一些进程,或者禁止一些访问等。
+
+* 3、数据库瓶颈
+
+ > * 如果慢查询比较多。那么就要开发人员或 DBA 协助进行 SQL 语句的优化。
+ > * 如果数据库响应慢,考虑可以加一个数据库缓存,如 Redis 等。然后,也可以搭建 MySQL 主从,一台 MySQL 服务器负责写,其他几台从数据库负责读。
+
+* 4、网站开发代码没有优化好
+
+ > * 例如 SQL 语句没有优化,导致数据库读写相当耗时。
+
+**针对网站访问慢,怎么去排查?**
+
+* 1、首先要确定是用户端还是服务端的问题。当接到用户反馈访问慢,那边自己立即访问网站看看,如果自己这边访问快,基本断定是用户端问题,就需要耐心跟客户解释,协助客户解决问题。
+
+ > 不要上来就看服务端的问题。一定要从源头开始,逐步逐步往下。
+
+* 2、如果访问也慢,那么可以利用浏览器的调试功能,看看加载那一项数据消耗时间过多,是图片加载慢,还是某些数据加载慢。
+
+* 3、针对服务器负载情况。查看服务器硬件(网络、CPU、内存)的消耗情况。如果是购买的云主机,比如阿里云,可以登录阿里云平台提供各方面的监控,比如 CPU、内存、带宽的使用情况。
+
+* 4、如果发现硬件资源消耗都不高,那么就需要通过查日志,比如看看 MySQL慢查询的日志,看看是不是某条 SQL 语句查询慢,导致网站访问慢。
+
+**怎么去解决?**
+
+* 1、如果是出口带宽问题,那么久申请加大出口带宽。
+* 2、如果慢查询比较多,那么就要开发人员或 DBA 协助进行 SQL 语句的优化。
+* 3、如果数据库响应慢,考虑可以加一个数据库缓存,如 Redis 等等。然后也可以搭建MySQL 主从,一台 MySQL 服务器负责写,其他几台从数据库负责读。
+* 4、申请购买 CDN 服务,加载用户的访问。
+* 5、如果访问还比较慢,那就需要从整体架构上进行优化咯。做到专角色专用,多台服务器提供同一个服务。
+
+### Linux 性能调优都有哪几种方法?
+
+* 1、Disabling daemons (关闭 daemons)。
+* 2、Shutting down the GUI (关闭 GUI)。
+* 3、Changing kernel parameters (改变内核参数)。
+* 4、Kernel parameters (内核参数)。
+* 5、Tuning the processor subsystem (处理器子系统调优)。
+* 6、Tuning the memory subsystem (内存子系统调优)。
+* 7、Tuning the file system (文件系统子系统调优)。
+* 8、Tuning the network subsystem(网络子系统调优)。
+
+## 基本命令
+
+###### cd (change directory:英文释义是改变目录)切换目录
+
+cd ../ ;跳到上级目录
+cd /opt ;不管现在到那直接跳到指定的opt文件夹中
+cd ~ ;切换当前用户的家目录。root用户的家目录就是root目录。
+复制代码
+
+###### pwd (print working directory:显示当前工作目录的绝对路径)
+
+pwd
+显示当前的绝对路劲
+复制代码
+
+###### ls (ls:list的缩写,查看列表)查看当前目录下的所有文件夹(ls 只列出文件名或目录名)
+
+ls -a ;显示所有文件夹,隐藏文件也显示出来
+ls -R ;连同子目录一起列出来
+复制代码
+
+###### ll (ll:list的缩写,查看列表详情)查看当前目录下的所有详细信息和文件夹(ll 结果是详细,有时间,是否可读写等信息)
+
+ll -a ;显示所有文件,隐藏文件也显示出来
+ll -R ;连同子目录内容一起列出来
+ll -h ;友好展示详情信息,可以看大小
+ll -al ;即能显示隐藏文件又能显示详细列表。
+复制代码
+
+###### touch (touch:创建文件)创建文件
+
+touch test.txt ;创建test.txt文件
+touch /opt/java/test.java ;在指定目录创建test.java文件
+复制代码
+
+###### mkdir (mkdir:创建目录) 创建目录
+
+mkdir 文件夹名称 ;在此目录创建文件夹
+mkdir /opt/java/jdk ;在指定目录创建文件夹
+复制代码
+
+###### cat (concatenate:显示或把多个文本文件连接起来)查看文件命令(可以快捷查看当前文件的内容)(不能快速定位到最后一页)
+
+cat lj.log ;快捷查看文件命令
+Ctrl + c ;暂停显示文件
+Ctrl + d ;退出查看文件命令
+复制代码
+
+###### more (more:更多的意思)分页查看文件命令(不能快速定位到最后一页)
+
+回车:向下n行,需要定义,默认为1行。
+空格键:向下滚动一屏或Ctrl+F
+B:返回上一层或Ctrl+B
+q:退出more
+复制代码
+
+###### less (lese:较少的意思)分页查看文件命令(可以快速定位到最后一页)
+
+less -m 显示类似于more命令的百分比。
+less -N 显示每行的行号。(大写的N)
+两参数一起使用如:less -mN 文件名,如此可分页并显示行号。
+
+空格键:前下一页或page down。
+回车:向下一行。
+b:后退一页 或 page up。
+q:退出。
+d:前进半页。
+u:后退半页
+复制代码
+
+###### tail(尾巴) 查看文件命令(看最后多少行)
+
+tail -10 ;文件名 看最后10行
+复制代码
+
+###### cp(copy单词缩写,复制功能)
+
+cp /opt/java/java.log /opt/logs/ ;把java.log 复制到/opt/logs/下
+cp /opt/java/java.log /opt/logs/aaa.log ;把java.log 复制到/opt/logs/下并且改名为aaa.log
+cp -r /opt/java /opt/logs ;把文件夹及内容复制到logs文件中
+复制代码
+
+###### mv(move单词缩写,移动功能,该文件名称功能)
+
+mv /opt/java/java.log /opt/mysql/ ;移动文件到mysql目录下
+mv java.log mysql.log ;把java.log改名为mysql.log
+复制代码
+
+###### rm(remove:移除的意思)删除文件,或文件夹
+
+-f或--force 强制删除文件或目录。删除文件不包括文件夹的文件
+-r或-R或--recursive 递归处理,将指定目录下的所有文件及子目录一并删除。
+-rf 强制删除文件夹及内容
+
+rm 文件名 ;安全删除命令 (yes删除 no取消)
+rm -rf 强制删除文件夹及内容
+rm -rf * 删除当前目录下的所有内容。
+rm -rf /* 删除Linux系统根目录下所有的内容。系统将完蛋。
+复制代码
+
+###### find (find:找到的意思)查找指定文件或目录
+
+* 表示0~多个任意字符。
+
+find -name 文件名;按照指定名称查找在当前目录下查找文件
+find / -name 文件名按照指定名称全局查找文件
+find -name '*文件名' ;任意前缀加上文件名在当前目录下查找文件
+find / -name '*文件名*' ;全局进行模糊查询带文件名的文件
+复制代码
+
+###### vi (VIsual:视觉)文本编辑器 类似win的记事本 (操作类似于地下的vim命令,看底下vim 的操作)
+
+###### vim (VI IMproved:改进版视觉)改进版文本编辑器 (不管是文件查看还是文件编辑 按 Shift + 上或者下可以上下移动查看视角)
+
+输入”vim 文件名” 打开文件,刚刚时是”一般模式”。
+
+一般模式:可以浏览文件内容,可以进行文本快捷操作。如单行复制,多行复制,单行删除,多行删除,(退出)等。
+插入模式:可以编辑文件内容。
+底行模式:可以进行强制退出操作,不保存 :q!
+ 可以进行保存并退出操作 :wq
+
+按下”i”或”a”或”o”键,从”一般模式”,进入”插入模式(编辑模式)”。
+在编辑模式下按”Esc” 即可到一般模式
+在一般模式下按”:”,冒号进入底行模式。
+
+在一般模式下的快捷键
+ dd ;删除一整行
+ X ;向前删除 等同于windowns系统中的删除键
+ x ;向后删除和大写x相反方向
+ Ctrl + f ;向后看一页
+ Ctrl + b ;向前看一页
+ u ;撤销上一步操作
+ /word ;向下查找word关键字 输入:n查找下一个,N查找上一个(不管是哪个查找都是全局查找 只不过n的方向相反)
+ ?log ;向上查找log关键字 输入:n查找上一个,N查找下一个
+ :1,90s/redis/Redis/g ;把1-90行的redis替换为Redis。语法n1,n2s/原关键字/新关键字/g,n1代表其实行,n2代表结尾行,g是必须要的
+ :0 ;光标移动到第一行
+ :$ ;光标移动到最后一行
+ :300 ;光标移动到300行,输入多少数字移动到多少行
+ :w ;保存
+ :w! ;强制保存
+ :q ;退出
+ :q! ;强制退出
+ 5dd ;删除后面5行,打一个参数为自己填写
+ 5x ;删除此光标后面5个字符
+ d1G ;删除此光标之前的所有
+ d0 ;从光标当前位置删除到此行的第一个位置
+ yy ;复制
+ p ;在光标的下面进行粘贴
+ P ;在光标的上门进行粘贴
+复制代码
+
+###### | 管道命令(把多个命令组合起来使用)
+
+管道命令的语法:命令1 | 命令2 | 命令3。
+复制代码
+
+###### grep (grep :正则表达式)正则表达式,用于字符串的搜索工作(模糊查询)。不懂可以先过
+
+单独使用:
+grep String test.java ;在test.java文件中查找String的位置,返回整行
+一般此命令不会单独使用下面列几个常用的命令(地下通过管道命令组合起来使用)
+
+ps aux|grep java ;查找带java关键字的进程
+ll |grep java ;查找带java关键字的文件夹及文件
+复制代码
+
+###### yum install -y lrzsz 命令(实现win到Linux文件互相简单上传文件)
+
+#(实际上就是在Linux系统中下载了一个插件)下了了此安装包后就可以实现win系统到linux之间拉文件拉文件
+#等待下载完了就可以输入:
+
+rz 从win系统中选择文件上传到Linux系统中
+
+sz 文件名 选择Linux系统的文件复制到win系统中
+
+复制代码
+
+###### tar (解压 压缩 命令)
+
+常用的组合命令:
+-z 是否需要用gzip压缩。
+-c 建立一个压缩文件的参数指令(create) –压缩
+ -x 解开一个压缩文件的参数指令(extract) –解压
+ -v 压缩的过程中显示文件(verbose)
+ -f 使用档名,在f之后要立即接档中(file)
+ 常用解压参数组合:zxvf
+ 常用压缩参数组合:zcvf
+
+解压命令:
+tar -zxvf redis-3.2.8.tar.gz ;解压到当前文件夹
+tar -zxvf redis-3.2.8.tar.gz -C /opt/java/ ;解压到指定目录
+
+压缩命令:(注意 语法有点反了,我反正每次都搞反)
+tar -zcvf redis-3.2.8.tar.gz redis-3.2.8/ ;语法 tar -zcvf 压缩后的名称 要压缩的文件
+tar -zcvf 压缩后的文件(可指定目录) 要压缩的文件(可指定目录)
+复制代码
+
+###### ps (process status:进程状态,类似于windows的任务管理器)
+
+常用组合:ps -ef 标准的格式查看系统进程
+ ps -aux BSD格式查看系统进程
+ ps -aux|grep redis BSD格式查看进程名称带有redis的系统进程(常用技巧)
+//显示进程的一些属性,需要了解(ps aux)
+USER //用户名
+PID //进程ID号,用来杀死进程的
+%CPU //进程占用的CPU的百分比
+%MEM //占用内存的的百分比
+VSZ //该进程使用的虚拟內存量(KB)
+RSS //该进程占用的固定內存量(KB)
+STAT //进程的状态
+START //该进程被触发启动时间
+TIME //该进程实际使用CPU运行的时间
+复制代码
+
+###### clear 清屏命令。(强迫症患者使用)
+
+kill 命令用来中止一个进程。(要配合ps命令使用,配合pid关闭进程)
+(ps类似于打开任务管理器,kill类似于关闭进程)
+ kill -5 进程的PID ;推荐,和平关闭进程
+ kill -9 PID ;不推荐,强制杀死进程
+复制代码
+
+###### ifconfig命令
+
+用于查看和更改网络接口的地址和参数,包括IP地址、网络掩码、广播地址,使用权限是超级用户。(一般是用来查看的,很少更改)
+如果此命令输入无效,先输入yum -y install net-tools
+ifconfig
+复制代码
+
+###### ping (用于检测与目标的连通性)语法:ping ip地址
+
+测试:
+1、在Windows操作系统中cmdipconfig,查看本机IP地址:
+2、再到LInux系统中输入 ping ip地址
+(公司电脑,我就不暴露Ip了,没图片 自己去试)
+按Ctrl + C 可以停止测试。
+复制代码
+
+###### free 命令 (显示系统内存)
+
+#显示系统内存使用情况,包括物理内存、交互区内存(swap)和内核缓冲区内存。
+-b 以Byte显示内存使用情况
+-k 以kb为单位显示内存使用情况
+-m 以mb为单位显示内存使用情况
+-g 以gb为单位显示内存使用情况
+-s<间隔秒数> 持续显示内存
+-t 显示内存使用总合
+复制代码
+
+###### top 命令
+
+#显示当前系统正在执行的进程的相关信息,包括进程 ID、内存占用率、CPU 占用率等
+-c 显示完整的进程命令
+-s 保密模式
+-p <进程号> 指定进程显示
+-n <次数>循环显示次数
+复制代码
+
+###### netstat 命令
+
+#Linux netstat命令用于显示网络状态。
+#利用netstat指令可让你得知整个Linux系统的网络情况。
+#语法:
+netstat [-acCeFghilMnNoprstuvVwx][-A<网络类型>][--ip]
+复制代码
+
+###### file (可查看文件类型)
+
+file 文件名
+复制代码
+
+###### 重启linux
+
+Linux centos 重启命令:reboot
+复制代码
+
+###### 关机linux
+
+Linux centos 关机命令:halt
+
+复制代码
+
+###### 同步时间命令
+
+ntpdate ntp1.aliyun.com
+复制代码
+
+###### 更改为北京时间命令
+
+rm -rf /etc/localtime
+ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
+复制代码
+
+###### 查看时间命令:
+
+date
+
+作者:小杰要吃蛋
+链接:https://juejin.cn/post/6844904127059738637
+来源:稀土掘金
+著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
\ No newline at end of file
diff --git "a/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Nginx.md" "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Nginx.md"
new file mode 100644
index 0000000..a1d00e1
--- /dev/null
+++ "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Nginx.md"
@@ -0,0 +1,516 @@
+
+
+### 什么是Nginx?
+
+* Nginx是一个 轻量级/高性能的反向代理Web服务器,他实现非常高效的反向代理、负载平衡,他可以处理2-3万并发连接数,官方监测能支持5万并发,现在中国使用nginx网站用户有很多,例如:新浪、网易、 腾讯等。
+
+### 为什么要用Nginx?
+
+* 跨平台、配置简单、方向代理、高并发连接:处理2-3万并发连接数,官方监测能支持5万并发,内存消耗小:开启10个nginx才占150M内存 ,nginx处理静态文件好,耗费内存少,
+
+* 而且Nginx内置的健康检查功能:如果有一个服务器宕机,会做一个健康检查,再发送的请求就不会发送到宕机的服务器了。重新将请求提交到其他的节点上。
+
+* 使用Nginx的话还能:
+
+ 1. 节省宽带:支持GZIP压缩,可以添加浏览器本地缓存
+ 2. 稳定性高:宕机的概率非常小
+ 3. 接收用户请求是异步的
+
+### 为什么Nginx性能这么高?
+
+* 因为他的事件处理机制:异步非阻塞事件处理机制:运用了epoll模型,提供了一个队列,排队解决
+
+### Nginx怎么处理请求的?
+
+* nginx接收一个请求后,首先由listen和server_name指令匹配server模块,再匹配server模块里的location,location就是实际地址
+
+```java
+server { # 第一个Server区块开始,表示一个独立的虚拟主机站点
+ listen 80; # 提供服务的端口,默认80
+ server_name localhost; # 提供服务的域名主机名
+ location / { # 第一个location区块开始
+ root html; # 站点的根目录,相当于Nginx的安装目录
+ index index.html index.htm; # 默认的首页文件,多个用空格分开
+ } # 第一个location区块结果
+ }
+```
+### 什么是正向代理和反向代理?
+
+1. 正向代理就是一个人发送一个请求直接就到达了目标的服务器
+2. 反方代理就是请求统一被Nginx接收,nginx反向代理服务器接收到之后,按照一定的规 则分发给了后端的业务处理服务器进行处理了
+
+### 使用“反向代理服务器的优点是什么?
+
+* 反向代理服务器可以隐藏源服务器的存在和特征。它充当互联网云和web服务器之间的中间层。这对于安全方面来说是很好的,特别是当您使用web托管服务时。
+
+### Nginx的优缺点?
+
+* 优点:
+
+ 1. 占内存小,可实现高并发连接,处理响应快
+ 2. 可实现http服务器、虚拟主机、方向代理、负载均衡
+ 3. Nginx配置简单
+ 4. 可以不暴露正式的服务器IP地址
+* 缺点: 动态处理差:nginx处理静态文件好,耗费内存少,但是处理动态页面则很鸡肋,现在一般前端用nginx作为反向代理抗住压力,
+
+### Nginx应用场景?
+
+1. http服务器。Nginx是一个http服务可以独立提供http服务。可以做网页静态服务器。
+2. 虚拟主机。可以实现在一台服务器虚拟出多个网站,例如个人网站使用的虚拟机。
+3. 反向代理,负载均衡。当网站的访问量达到一定程度后,单台服务器不能满足用户的请求时,需要用多台服务器集群可以使用nginx做反向代理。并且多台服务器可以平均分担负载,不会应为某台服务器负载高宕机而某台服务器闲置的情况。
+4. nginz 中也可以配置安全管理、比如可以使用Nginx搭建API接口网关,对每个接口服务进行拦截。
+
+### Nginx目录结构有哪些?
+
+```java
+[root@localhost ~]# tree /usr/local/nginx
+/usr/local/nginx
+├── client_body_temp
+├── conf # Nginx所有配置文件的目录
+│ ├── fastcgi.conf # fastcgi相关参数的配置文件
+│ ├── fastcgi.conf.default # fastcgi.conf的原始备份文件
+│ ├── fastcgi_params # fastcgi的参数文件
+│ ├── fastcgi_params.default
+│ ├── koi-utf
+│ ├── koi-win
+│ ├── mime.types # 媒体类型
+│ ├── mime.types.default
+│ ├── nginx.conf # Nginx主配置文件
+│ ├── nginx.conf.default
+│ ├── scgi_params # scgi相关参数文件
+│ ├── scgi_params.default
+│ ├── uwsgi_params # uwsgi相关参数文件
+│ ├── uwsgi_params.default
+│ └── win-utf
+├── fastcgi_temp # fastcgi临时数据目录
+├── html # Nginx默认站点目录
+│ ├── 50x.html # 错误页面优雅替代显示文件,例如当出现502错误时会调用此页面
+│ └── index.html # 默认的首页文件
+├── logs # Nginx日志目录
+│ ├── access.log # 访问日志文件
+│ ├── error.log # 错误日志文件
+│ └── nginx.pid # pid文件,Nginx进程启动后,会把所有进程的ID号写到此文件
+├── proxy_temp # 临时目录
+├── sbin # Nginx命令目录
+│ └── nginx # Nginx的启动命令
+├── scgi_temp # 临时目录
+└── uwsgi_temp # 临时目录
+```
+### Nginx配置文件nginx.conf有哪些属性模块?
+
+```java
+worker_processes 1; # worker进程的数量
+events { # 事件区块开始
+ worker_connections 1024; # 每个worker进程支持的最大连接数
+} # 事件区块结束
+http { # HTTP区块开始
+ include mime.types; # Nginx支持的媒体类型库文件
+ default_type application/octet-stream; # 默认的媒体类型
+ sendfile on; # 开启高效传输模式
+ keepalive_timeout 65; # 连接超时
+ server { # 第一个Server区块开始,表示一个独立的虚拟主机站点
+ listen 80; # 提供服务的端口,默认80
+ server_name localhost; # 提供服务的域名主机名
+ location / { # 第一个location区块开始
+ root html; # 站点的根目录,相当于Nginx的安装目录
+ index index.html index.htm; # 默认的首页文件,多个用空格分开
+ } # 第一个location区块结果
+ error_page 500502503504 /50x.html; # 出现对应的http状态码时,使用50x.html回应客户
+ location = /50x.html { # location区块开始,访问50x.html
+ root html; # 指定对应的站点目录为html
+ }
+ }
+ ......
+```
+### Nginx静态资源?
+
+* 静态资源访问,就是存放在nginx的html页面,我们可以自己编写
+
+### 如何用Nginx解决前端跨域问题?
+
+* 使用Nginx转发请求。把跨域的接口写成调本域的接口,然后将这些接口转发到真正的请求地址。
+
+### Nginx虚拟主机怎么配置?
+
+* 1、基于域名的虚拟主机,通过域名来区分虚拟主机——应用:外部网站
+
+* 2、基于端口的虚拟主机,通过端口来区分虚拟主机——应用:公司内部网站,外部网站的管理后台
+
+* 3、基于ip的虚拟主机。
+
+#### 基于虚拟主机配置域名
+
+* 需要建立/data/www /data/bbs目录,windows本地hosts添加虚拟机ip地址对应的域名解析;对应域名网站目录下新增index.html文件;
+
+```java
+#当客户端访问www.lijie.com,监听端口号为80,直接跳转到data/www目录下文件
+ server {
+ listen 80;
+ server_name www.lijie.com;
+ location / {
+ root data/www;
+ index index.html index.htm;
+ }
+ }
+
+ #当客户端访问www.lijie.com,监听端口号为80,直接跳转到data/bbs目录下文件
+ server {
+ listen 80;
+ server_name bbs.lijie.com;
+ location / {
+ root data/bbs;
+ index index.html index.htm;
+ }
+ }
+```
+#### 基于端口的虚拟主机
+
+* 使用端口来区分,浏览器使用域名或ip地址:端口号 访问
+
+```java
+#当客户端访问www.lijie.com,监听端口号为8080,直接跳转到data/www目录下文件
+ server {
+ listen 8080;
+ server_name 8080.lijie.com;
+ location / {
+ root data/www;
+ index index.html index.htm;
+ }
+ }
+
+ #当客户端访问www.lijie.com,监听端口号为80直接跳转到真实ip服务器地址 127.0.0.1:8080
+ server {
+ listen 80;
+ server_name www.lijie.com;
+ location / {
+ proxy_pass http://127.0.0.1:8080;
+ index index.html index.htm;
+ }
+ }
+```
+### location的作用是什么?
+
+* location指令的作用是根据用户请求的URI来执行不同的应用,也就是根据用户请求的网站URL进行匹配,匹配成功即进行相关的操作。
+
+#### location的语法能说出来吗?
+
+> 注意:~ 代表自己输入的英文字母
+>
+> | 匹配符 | 匹配规则 | 优先级 |
+> | --- | --- | --- |
+> | = | 精确匹配 | 1 |
+> | ^~ | 以某个字符串开头 | 2 |
+> | ~ | 区分大小写的正则匹配 | 3 |
+> | ~* | 不区分大小写的正则匹配 | 4 |
+> | !~ | 区分大小写不匹配的正则 | 5 |
+> | !~* | 不区分大小写不匹配的正则 | 6 |
+> | / | 通用匹配,任何请求都会匹配到 | 7 |
+
+#### Location正则案例
+
+* 示例:
+
+```java
+#优先级1,精确匹配,根路径
+ location =/ {
+ return 400;
+ }
+
+ #优先级2,以某个字符串开头,以av开头的,优先匹配这里,区分大小写
+ location ^~ /av {
+ root /data/av/;
+ }
+
+ #优先级3,区分大小写的正则匹配,匹配/media*****路径
+ location ~ /media {
+ alias /data/static/;
+ }
+
+ #优先级4 ,不区分大小写的正则匹配,所有的****.jpg|gif|png 都走这里
+ location ~* .*\.(jpg|gif|png|js|css)$ {
+ root /data/av/;
+ }
+
+ #优先7,通用匹配
+ location / {
+ return 403;
+ }
+```
+### 限流怎么做的?
+
+* Nginx限流就是限制用户请求速度,防止服务器受不了
+
+* 限流有3种
+
+ 1. 正常限制访问频率(正常流量)
+ 2. 突发限制访问频率(突发流量)
+ 3. 限制并发连接数
+* Nginx的限流都是基于漏桶流算法,底下会说道什么是桶铜流
+
+**实现三种限流算法**
+
+##### 1、正常限制访问频率(正常流量):
+
+* 限制一个用户发送的请求,我Nginx多久接收一个请求。
+
+* Nginx中使用ngx_http_limit_req_module模块来限制的访问频率,限制的原理实质是基于漏桶算法原理来实现的。在nginx.conf配置文件中可以使用limit_req_zone命令及limit_req命令限制单个IP的请求处理频率。
+
+```java
+#定义限流维度,一个用户一分钟一个请求进来,多余的全部漏掉
+ limit_req_zone $binary_remote_addr zone=one:10m rate=1r/m;
+
+ #绑定限流维度
+ server{
+
+ location/seckill.html{
+ limit_req zone=zone;
+ proxy_pass http://lj_seckill;
+ }
+
+ }
+```
+
+* 1r/s代表1秒一个请求,1r/m一分钟接收一个请求, 如果Nginx这时还有别人的请求没有处理完,Nginx就会拒绝处理该用户请求。
+
+##### 2、突发限制访问频率(突发流量):
+
+* 限制一个用户发送的请求,我Nginx多久接收一个。
+
+* 上面的配置一定程度可以限制访问频率,但是也存在着一个问题:如果突发流量超出请求被拒绝处理,无法处理活动时候的突发流量,这时候应该如何进一步处理呢?Nginx提供burst参数结合nodelay参数可以解决流量突发的问题,可以设置能处理的超过设置的请求数外能额外处理的请求数。我们可以将之前的例子添加burst参数以及nodelay参数:
+
+```java
+#定义限流维度,一个用户一分钟一个请求进来,多余的全部漏掉
+ limit_req_zone $binary_remote_addr zone=one:10m rate=1r/m;
+
+ #绑定限流维度
+ server{
+
+ location/seckill.html{
+ limit_req zone=zone burst=5 nodelay;
+ proxy_pass http://lj_seckill;
+ }
+
+ }
+```
+
+* 为什么就多了一个 burst=5 nodelay; 呢,多了这个可以代表Nginx对于一个用户的请求会立即处理前五个,多余的就慢慢来落,没有其他用户的请求我就处理你的,有其他的请求的话我Nginx就漏掉不接受你的请求
+
+##### 3、 限制并发连接数
+
+* Nginx中的ngx_http_limit_conn_module模块提供了限制并发连接数的功能,可以使用limit_conn_zone指令以及limit_conn执行进行配置。接下来我们可以通过一个简单的例子来看下:
+
+```java
+http {
+ limit_conn_zone $binary_remote_addr zone=myip:10m;
+ limit_conn_zone $server_name zone=myServerName:10m;
+ }
+
+ server {
+ location / {
+ limit_conn myip 10;
+ limit_conn myServerName 100;
+ rewrite / http://www.lijie.net permanent;
+ }
+ }
+```
+
+* 上面配置了单个IP同时并发连接数最多只能10个连接,并且设置了整个虚拟服务器同时最大并发数最多只能100个链接。当然,只有当请求的header被服务器处理后,虚拟服务器的连接数才会计数。刚才有提到过Nginx是基于漏桶算法原理实现的,实际上限流一般都是基于漏桶算法和令牌桶算法实现的。接下来我们来看看两个算法的介绍:
+
+### 漏桶流算法和令牌桶算法知道?
+
+#### 漏桶算法
+
+* 漏桶算法是网络世界中流量整形或速率限制时经常使用的一种算法,它的主要目的是控制数据注入到网络的速率,平滑网络上的突发流量。漏桶算法提供了一种机制,通过它,突发流量可以被整形以便为网络提供一个稳定的流量。也就是我们刚才所讲的情况。漏桶算法提供的机制实际上就是刚才的案例:**突发流量会进入到一个漏桶,漏桶会按照我们定义的速率依次处理请求,如果水流过大也就是突发流量过大就会直接溢出,则多余的请求会被拒绝。所以漏桶算法能控制数据的传输速率。**
+
+#### 令牌桶算法
+
+* 令牌桶算法是网络流量整形和速率限制中最常使用的一种算法。典型情况下,令牌桶算法用来控制发送到网络上的数据的数目,并允许突发数据的发送。Google开源项目Guava中的RateLimiter使用的就是令牌桶控制算法。**令牌桶算法的机制如下:存在一个大小固定的令牌桶,会以恒定的速率源源不断产生令牌。如果令牌消耗速率小于生产令牌的速度,令牌就会一直产生直至装满整个令牌桶。**
+
+
+### 为什么要做动静分离?
+
+* Nginx是当下最热的Web容器,网站优化的重要点在于静态化网站,网站静态化的关键点则是是动静分离,动静分离是让动态网站里的动态网页根据一定规则把不变的资源和经常变的资源区分开来,动静资源做好了拆分以后,我们则根据静态资源的特点将其做缓存操作。
+
+* 让静态的资源只走静态资源服务器,动态的走动态的服务器
+
+* Nginx的静态处理能力很强,但是动态处理能力不足,因此,在企业中常用动静分离技术。
+
+* 对于静态资源比如图片,js,css等文件,我们则在反向代理服务器nginx中进行缓存。这样浏览器在请求一个静态资源时,代理服务器nginx就可以直接处理,无需将请求转发给后端服务器tomcat。 若用户请求的动态文件,比如servlet,jsp则转发给Tomcat服务器处理,从而实现动静分离。这也是反向代理服务器的一个重要的作用。
+
+### Nginx怎么做的动静分离?
+
+* 只需要指定路径对应的目录。location/可以使用正则表达式匹配。并指定对应的硬盘中的目录。如下:(操作都是在Linux上)
+
+```java
+location /image/ {
+ root /usr/local/static/;
+ autoindex on;
+ }
+```
+
+1. 创建目录
+
+```java
+mkdir /usr/local/static/image
+```
+1. 进入目录
+
+```java
+cd /usr/local/static/image
+```
+1. 放一张照片上去#
+
+```java
+1.jpg
+```
+1. 重启 nginx
+
+```java
+sudo nginx -s reload
+```
+1. 打开浏览器 输入 server_name/image/1.jpg 就可以访问该静态图片了
+
+### Nginx负载均衡的算法怎么实现的?策略有哪些?
+
+* 为了避免服务器崩溃,大家会通过负载均衡的方式来分担服务器压力。将对台服务器组成一个集群,当用户访问时,先访问到一个转发服务器,再由转发服务器将访问分发到压力更小的服务器。
+
+* Nginx负载均衡实现的策略有以下五种:
+
+#### 1 轮询(默认)
+
+* 每个请求按时间顺序逐一分配到不同的后端服务器,如果后端某个服务器宕机,能自动剔除故障系统。
+
+```java
+upstream backserver {
+ server 192.168.0.12;
+ server 192.168.0.13;
+}
+```
+#### 2 权重 weight
+
+* weight的值越大分配
+
+* 到的访问概率越高,主要用于后端每台服务器性能不均衡的情况下。其次是为在主从的情况下设置不同的权值,达到合理有效的地利用主机资源。
+
+```java
+upstream backserver {
+ server 192.168.0.12 weight=2;
+ server 192.168.0.13 weight=8;
+}
+```
+
+* 权重越高,在被访问的概率越大,如上例,分别是20%,80%。
+
+#### 3 ip_hash( IP绑定)
+
+* 每个请求按访问IP的哈希结果分配,使来自同一个IP的访客固定访问一台后端服务器,`并且可以有效解决动态网页存在的session共享问题`
+
+```java
+upstream backserver {
+ ip_hash;
+ server 192.168.0.12:88;
+ server 192.168.0.13:80;
+}
+```
+#### 4 fair(第三方插件)
+
+* 必须安装upstream_fair模块。
+
+* 对比 weight、ip_hash更加智能的负载均衡算法,fair算法可以根据页面大小和加载时间长短智能地进行负载均衡,响应时间短的优先分配。
+
+```java
+upstream backserver {
+ server server1;
+ server server2;
+ fair;
+}
+
+```
+
+* 哪个服务器的响应速度快,就将请求分配到那个服务器上。
+
+#### 5、url_hash(第三方插件)
+
+* 必须安装Nginx的hash软件包
+
+* 按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,可以进一步提高后端缓存服务器的效率。
+
+```java
+upstream backserver {
+ server squid1:3128;
+ server squid2:3128;
+ hash $request_uri;
+ hash_method crc32;
+}
+
+```
+### Nginx配置高可用性怎么配置?
+
+* 当上游服务器(真实访问服务器),一旦出现故障或者是没有及时相应的话,应该直接轮训到下一台服务器,保证服务器的高可用
+
+* Nginx配置代码:
+
+```java
+server {
+ listen 80;
+ server_name www.lijie.com;
+ location / {
+ ### 指定上游服务器负载均衡服务器
+ proxy_pass http://backServer;
+ ###nginx与上游服务器(真实访问的服务器)超时时间 后端服务器连接的超时时间_发起握手等候响应超时时间
+ proxy_connect_timeout 1s;
+ ###nginx发送给上游服务器(真实访问的服务器)超时时间
+ proxy_send_timeout 1s;
+ ### nginx接受上游服务器(真实访问的服务器)超时时间
+ proxy_read_timeout 1s;
+ index index.html index.htm;
+ }
+ }
+
+```
+### Nginx怎么判断别IP不可访问?
+
+```java
+# 如果访问的ip地址为192.168.9.115,则返回403
+if ($remote_addr = 192.168.9.115) {
+ return 403;
+}
+```
+### 怎么限制浏览器访问?
+
+```java
+## 不允许谷歌浏览器访问 如果是谷歌浏览器返回500
+if ($http_user_agent ~ Chrome) {
+ return 500;
+}
+```
+### Rewrite全局变量是什么?
+
+> | 变量 | 含义 |
+> | --- | --- |
+> | $args | 这个变量等于请求行中的参数,同$query_string |
+> | $content length | 请求头中的Content-length字段。 |
+> | $content_type | 请求头中的Content-Type字段。 |
+> | $document_root | 当前请求在root指令中指定的值。 |
+> | $host | 请求主机头字段,否则为服务器名称。 |
+> | $http_user_agent | 客户端agent信息 |
+> | $http_cookie | 客户端cookie信息 |
+> | $limit_rate | 这个变量可以限制连接速率。 |
+> | $request_method | 客户端请求的动作,通常为GET或POST。 |
+> | $remote_addr | 客户端的IP地址。 |
+> | $remote_port | 客户端的端口。 |
+> | $remote_user | 已经经过Auth Basic Module验证的用户名。 |
+> | $request_filename | 当前请求的文件路径,由root或alias指令与URI请求生成。 |
+> | $scheme | HTTP方法(如http,https)。 |
+> | $server_protocol | 请求使用的协议,通常是HTTP/1.0或HTTP/1.1。 |
+> | $server_addr | 服务器地址,在完成一次系统调用后可以确定这个值。 |
+> | $server_name | 服务器名称。 |
+> | $server_port | 请求到达服务器的端口号。 |
+> | $request_uri | 包含请求参数的原始URI,不包含主机名,如”/foo/bar.php?arg=baz”。 |
+> | $uri | 不带请求参数的当前URI,$uri不包含主机名,如”/foo/bar.html”。 |
+> | $document_uri | 与$uri相同。 |
+
+作者:小杰要吃蛋
+链接:https://juejin.cn/post/6844904125784653837
+来源:稀土掘金
+著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
\ No newline at end of file
diff --git "a/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/elasticSearch.md" "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/elasticSearch.md"
new file mode 100644
index 0000000..8c213a4
--- /dev/null
+++ "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/elasticSearch.md"
@@ -0,0 +1,296 @@
+https://juejin.cn/post/6844904032083902471
+
+## 前言
+
+ ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎。ElasticSearch用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。官方客户端在Java、.NET(C#)、PHP、Python、Apache Groovy、Ruby和许多其他语言中都是可用的。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr,也是基于Lucene。
+
+ 
+
+## Elasticsearch 面试题
+
+ 1、elasticsearch 了解多少,说说你们公司 es 的集群架构,索引数据大小,分片有多少,以及一些调优手段 。
+
+ 2、elasticsearch 的倒排索引是什么
+
+ 3、elasticsearch 索引数据多了怎么办,如何调优,部署
+
+ 4、elasticsearch 是如何实现 master 选举的
+
+ 5、详细描述一下 Elasticsearch 索引文档的过程
+
+ 6、详细描述一下 Elasticsearch 搜索的过程?
+
+ 7、Elasticsearch 在部署时,对 Linux 的设置有哪些优化方法
+
+ 8、lucence 内部结构是什么?
+
+ 9、Elasticsearch 是如何实现 Master 选举的?
+
+ 10、Elasticsearch 中的节点(比如共 20 个),其中的 10 个选了一个master,另外 10 个选了另一个 master,怎么办?
+
+ 11、客户端在和集群连接时,如何选择特定的节点执行请求的?
+
+ 12、详细描述一下 Elasticsearch 索引文档的过程。
+
+ 
+
+## 1、elasticsearch 了解多少,说说你们公司 es 的集群架构,索引数据大小,分片有多少,以及一些调优手段 。
+
+ 面试官:想了解应聘者之前公司接触的 ES 使用场景、规模,有没有做过比较大规模的索引设计、规划、调优。
+
+ 解答:如实结合自己的实践场景回答即可。
+
+ 比如:ES 集群架构 13 个节点,索引根据通道不同共 20+索引,根据日期,每日递增 20+,索引:10 分片,每日递增 1 亿+数据,每个通道每天索引大小控制:150GB 之内。
+
+ 仅索引层面调优手段:
+
+### 1.1、设计阶段调优
+
+ (1)根据业务增量需求,采取基于日期模板创建索引,通过 roll over API 滚动索引;
+
+ (2)使用别名进行索引管理;
+
+ (3)每天凌晨定时对索引做 force_merge 操作,以释放空间;
+
+ (4)采取冷热分离机制,热数据存储到 SSD,提高检索效率;冷数据定期进行 shrink操作,以缩减存储;
+
+ (5)采取 curator 进行索引的生命周期管理;
+
+ (6)仅针对需要分词的字段,合理的设置分词器;
+
+ (7)Mapping 阶段充分结合各个字段的属性,是否需要检索、是否需要存储等。……..
+
+### 1.2、写入调优
+
+ (1)写入前副本数设置为 0;
+
+ (2)写入前关闭 refresh_interval 设置为-1,禁用刷新机制;
+
+ (3)写入过程中:采取 bulk 批量写入;
+
+ (4)写入后恢复副本数和刷新间隔;
+
+ (5)尽量使用自动生成的 id。
+
+### 1.3、查询调优
+
+ (1)禁用 wildcard;
+
+ (2)禁用批量 terms(成百上千的场景);
+
+ (3)充分利用倒排索引机制,能 keyword 类型尽量 keyword;
+
+ (4)数据量大时候,可以先基于时间敲定索引再检索;
+
+ (5)设置合理的路由机制。
+
+### 1.4、其他调优
+
+ 部署调优,业务调优等。
+
+ 上面的提及一部分,面试者就基本对你之前的实践或者运维经验有所评估了。
+
+## 2、elasticsearch 的倒排索引是什么
+
+ 面试官:想了解你对基础概念的认知。
+
+ 解答:通俗解释一下就可以。
+
+ 传统的我们的检索是通过文章,逐个遍历找到对应关键词的位置。
+
+ 而倒排索引,是通过分词策略,形成了词和文章的映射关系表,这种词典+映射表即为倒排索引。有了倒排索引,就能实现 o(1)时间复杂度的效率检索文章了,极大的提高了检索效率。
+
+ 
+
+ 学术的解答方式:
+
+ 倒排索引,相反于一篇文章包含了哪些词,它从词出发,记载了这个词在哪些文档中出现过,由两部分组成——词典和倒排表。
+
+ 加分项:倒排索引的底层实现是基于:FST(Finite State Transducer)数据结构。
+
+ lucene 从 4+版本后开始大量使用的数据结构是 FST。FST 有两个优点:
+
+ (1)空间占用小。通过对词典中单词前缀和后缀的重复利用,压缩了存储空间;
+
+ (2)查询速度快。O(len(str))的查询时间复杂度。
+
+## 3、elasticsearch 索引数据多了怎么办,如何调优,部署
+
+ 面试官:想了解大数据量的运维能力。
+
+ 解答:索引数据的规划,应在前期做好规划,正所谓“设计先行,编码在后”,这样才能有效的避免突如其来的数据激增导致集群处理能力不足引发的线上客户检索或者其他业务受到影响。
+
+ 如何调优,正如问题 1 所说,这里细化一下:
+
+### 3.1 动态索引层面
+
+ 基于模板+时间+rollover api 滚动创建索引,举例:设计阶段定义:blog 索引的模板格式为:blog_index_时间戳的形式,每天递增数据。这样做的好处:不至于数据量激增导致单个索引数据量非常大,接近于上线 2 的32 次幂-1,索引存储达到了 TB+甚至更大。
+
+ 一旦单个索引很大,存储等各种风险也随之而来,所以要提前考虑+及早避免。
+
+### 3.2 存储层面
+
+ 冷热数据分离存储,热数据(比如最近 3 天或者一周的数据),其余为冷数据。
+
+ 对于冷数据不会再写入新数据,可以考虑定期 force_merge 加 shrink 压缩操作,节省存储空间和检索效率。
+
+### 3.3 部署层面
+
+ 一旦之前没有规划,这里就属于应急策略。
+
+ 结合 ES 自身的支持动态扩展的特点,动态新增机器的方式可以缓解集群压力,注意:如果之前主节点等规划合理,不需要重启集群也能完成动态新增的。
+
+## 4、elasticsearch 是如何实现 master 选举的
+
+ 面试官:想了解 ES 集群的底层原理,不再只关注业务层面了。
+
+ 解答:
+
+ 前置前提:
+
+ (1)只有候选主节点(master:true)的节点才能成为主节点。
+
+ (2)最小主节点数(min_master_nodes)的目的是防止脑裂。
+
+ 核对了一下代码,核心入口为 findMaster,选择主节点成功返回对应 Master,否则返回 null。选举流程大致描述如下:
+
+ 第一步:确认候选主节点数达标,elasticsearch.yml 设置的值
+
+ discovery.zen.minimum_master_nodes;
+
+ 第二步:比较:先判定是否具备 master 资格,具备候选主节点资格的优先返回;
+
+ 若两节点都为候选主节点,则 id 小的值会主节点。注意这里的 id 为 string 类型。
+
+ 题外话:获取节点 id 的方法。
+
+1GET /_cat/nodes?v&h=ip,port,heapPercent,heapMax,id,name
+
+2ip port heapPercent heapMax id name复制代码
+## 5、详细描述一下 Elasticsearch 索引文档的过程
+
+ 面试官:想了解 ES 的底层原理,不再只关注业务层面了。
+
+ 解答:
+
+ 这里的索引文档应该理解为文档写入 ES,创建索引的过程。
+
+ 文档写入包含:单文档写入和批量 bulk 写入,这里只解释一下:单文档写入流程。
+
+ 记住官方文档中的这个图。
+
+ 
+
+ 第一步:客户写集群某节点写入数据,发送请求。(如果没有指定路由/协调节点,请求的节点扮演路由节点的角色。)
+
+ 第二步:节点 1 接受到请求后,使用文档_id 来确定文档属于分片 0。请求会被转到另外的节点,假定节点 3。因此分片 0 的主分片分配到节点 3 上。
+
+ 第三步:节点 3 在主分片上执行写操作,如果成功,则将请求并行转发到节点 1和节点 2 的副本分片上,等待结果返回。所有的副本分片都报告成功,节点 3 将向协调节点(节点 1)报告成功,节点 1 向请求客户端报告写入成功。
+
+ 如果面试官再问:第二步中的文档获取分片的过程?
+
+ 回答:借助路由算法获取,路由算法就是根据路由和文档 id 计算目标的分片 id 的过程。
+
+1shard = hash(_routing) % (num_of_primary_shards)复制代码
+## 6、详细描述一下 Elasticsearch 搜索的过程?
+
+ 面试官:想了解 ES 搜索的底层原理,不再只关注业务层面了。
+
+ 解答:
+
+ 搜索拆解为“query then fetch” 两个阶段。
+
+ query 阶段的目的:定位到位置,但不取。
+
+ 步骤拆解如下:
+
+ (1)假设一个索引数据有 5 主+1 副本 共 10 分片,一次请求会命中(主或者副本分片中)的一个。
+
+ (2)每个分片在本地进行查询,结果返回到本地有序的优先队列中。
+
+ (3)第 2)步骤的结果发送到协调节点,协调节点产生一个全局的排序列表。
+
+ fetch 阶段的目的:取数据。
+
+ 路由节点获取所有文档,返回给客户端。
+
+ 
+
+## 7、Elasticsearch 在部署时,对 Linux 的设置有哪些优化方法
+
+ 面试官:想了解对 ES 集群的运维能力。
+
+ 解答:
+
+ (1)关闭缓存 swap;
+
+ (2)堆内存设置为:Min(节点内存/2, 32GB);
+
+ (3)设置最大文件句柄数;
+
+ (4)线程池+队列大小根据业务需要做调整;
+
+ (5)磁盘存储 raid 方式——存储有条件使用 RAID10,增加单节点性能以及避免单节点存储故障。
+
+## 8、lucence 内部结构是什么?
+
+ 面试官:想了解你的知识面的广度和深度。
+
+ 解答:
+
+ 
+
+ Lucene 是有索引和搜索的两个过程,包含索引创建,索引,搜索三个要点。可以基于这个脉络展开一些。
+
+## 9、Elasticsearch 是如何实现 Master 选举的?
+
+ (1)Elasticsearch 的选主是 ZenDiscovery 模块负责的,主要包含 Ping(节点之间通过这个 RPC 来发现彼此)和 Unicast(单播模块包含一个主机列表以控制哪些节点需要 ping 通)这两部分;
+
+ (2)对所有可以成为 master 的节点(node.master: true)根据 nodeId 字典排序,每次选举每个节点都把自己所知道节点排一次序,然后选出第一个(第 0 位)节点,暂且认为它是 master 节点。
+
+ (3)如果对某个节点的投票数达到一定的值(可以成为 master 节点数 n/2+1)并且该节点自己也选举自己,那这个节点就是 master。否则重新选举一直到满足上述条件。
+
+ (4)补充:master 节点的职责主要包括集群、节点和索引的管理,不负责文档级别的管理;data 节点可以关闭 http 功能*。
+
+## 10、Elasticsearch 中的节点(比如共 20 个),其中的 10 个
+
+ 选了一个 master,另外 10 个选了另一个 master,怎么办?
+
+ (1)当集群 master 候选数量不小于 3 个时,可以通过设置最少投票通过数量(discovery.zen.minimum_master_nodes)超过所有候选节点一半以上来解决脑裂问题;
+
+ (3)当候选数量为两个时,只能修改为唯一的一个 master 候选,其他作为 data节点,避免脑裂问题。
+
+## 11、客户端在和集群连接时,如何选择特定的节点执行请求的?
+
+ TransportClient 利用 transport 模块远程连接一个 elasticsearch 集群。它并不加入到集群中,只是简单的获得一个或者多个初始化的 transport 地址,并以 轮询 的方式与这些地址进行通信。
+
+## 12、详细描述一下 Elasticsearch 索引文档的过程。
+
+ 协调节点默认使用文档 ID 参与计算(也支持通过 routing),以便为路由提供合适的分片。
+
+shard = hash(document_id) % (num_of_primary_shards)复制代码
+
+ (1)当分片所在的节点接收到来自协调节点的请求后,会将请求写入到 MemoryBuffer,然后定时(默认是每隔 1 秒)写入到 Filesystem Cache,这个从 MomeryBuffer 到 Filesystem Cache 的过程就叫做 refresh;
+
+ (2)当然在某些情况下,存在 Momery Buffer 和 Filesystem Cache 的数据可能会丢失,ES 是通过 translog 的机制来保证数据的可靠性的。其实现机制是接收到请求后,同时也会写入到 translog 中 ,当 Filesystem cache 中的数据写入到磁盘中时,才会清除掉,这个过程叫做 flush;
+
+ (3)在 flush 过程中,内存中的缓冲将被清除,内容被写入一个新段,段的 fsync将创建一个新的提交点,并将内容刷新到磁盘,旧的 translog 将被删除并开始一个新的 translog。
+
+ (4)flush 触发的时机是定时触发(默认 30 分钟)或者 translog 变得太大(默认为 512M)时;
+
+ 
+
+ 补充:关于 Lucene 的 Segement:
+
+ (1)Lucene 索引是由多个段组成,段本身是一个功能齐全的倒排索引。
+
+ (2)段是不可变的,允许 Lucene 将新的文档增量地添加到索引中,而不用从头重建索引。
+
+ (3)对于每一个搜索请求而言,索引中的所有段都会被搜索,并且每个段会消耗CPU 的时钟周、文件句柄和内存。这意味着段的数量越多,搜索性能会越低。
+
+ (4)为了解决这个问题,Elasticsearch 会合并小段到一个较大的段,提交新的合并段到磁盘,并删除那些旧的小段。
+
+作者:程序员追风
+链接:https://juejin.cn/post/6844904031555420167
+来源:稀土掘金
+著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
\ No newline at end of file
diff --git "a/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/kafka.md" "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/kafka.md"
new file mode 100644
index 0000000..677057b
--- /dev/null
+++ "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/kafka.md"
@@ -0,0 +1 @@
+https://juejin.cn/post/6844904025805029383
\ No newline at end of file
diff --git "a/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/mybatis.md" "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/mybatis.md"
new file mode 100644
index 0000000..b648d26
--- /dev/null
+++ "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/mybatis.md"
@@ -0,0 +1,565 @@
+
+
+## MyBatis简介
+
+### MyBatis是什么?
+
+* Mybatis 是一个半 ORM(对象关系映射)框架,它内部封装了 JDBC,开发时只需要关注 SQL 语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement 等繁杂的过程。程序员直接编写原生态 sql,可以严格控制 sql 执行性能,灵活度高。
+
+* MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO 映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
+
+### Mybatis优缺点
+
+**优点**
+
+`与传统的数据库访问技术相比,ORM有以下优点:`
+
+* 基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用
+* 与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接
+* 很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)
+* 提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护
+* 能够与Spring很好的集成
+
+**缺点**
+
+* SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求
+* SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库
+
+### Hibernate 和 MyBatis 的区别
+
+**相同点**
+
+* 都是对jdbc的封装,都是持久层的框架,都用于dao层的开发。
+
+**不同点**
+
+* 映射关系
+ * MyBatis 是一个半自动映射的框架,配置Java对象与sql语句执行结果的对应关系,多表关联关系配置简单
+ * Hibernate 是一个全表映射的框架,配置Java对象与数据库表的对应关系,多表关联关系配置复杂
+
+**SQL优化和移植性**
+
+* Hibernate 对SQL语句封装,提供了日志、缓存、级联(级联比 MyBatis 强大)等特性,此外还提供 HQL(Hibernate Query Language)操作数据库,数据库无关性支持好,但会多消耗性能。如果项目需要支持多种数据库,代码开发量少,但SQL语句优化困难。
+* MyBatis 需要手动编写 SQL,支持动态 SQL、处理列表、动态生成表名、支持存储过程。开发工作量相对大些。直接使用SQL语句操作数据库,不支持数据库无关性,但sql语句优化容易。
+
+### ORM是什么
+
+* ORM(Object Relational Mapping),对象关系映射,是一种为了解决关系型数据库数据与简单Java对象(POJO)的映射关系的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系型数据库中。
+
+### 为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?
+
+* Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。
+
+* 而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具。
+
+### 传统JDBC开发存在什么问题?
+
+* 频繁创建数据库连接对象、释放,容易造成系统资源浪费,影响系统性能。可以使用连接池解决这个问题。但是使用jdbc需要自己实现连接池。
+* sql语句定义、参数设置、结果集处理存在硬编码。实际项目中sql语句变化的可能性较大,一旦发生变化,需要修改java代码,系统需要重新编译,重新发布。不好维护。
+* 使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
+* 结果集处理存在重复代码,处理麻烦。如果可以映射成Java对象会比较方便。
+
+### JDBC编程有哪些不足之处,MyBatis是如何解决的?
+
+* 1、数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接池可解决此问题。
+
+ * 解决:在mybatis-config.xml中配置数据链接池,使用连接池管理数据库连接。
+* 2、Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。-
+
+ * 解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。
+* 3、向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
+
+ * 解决: Mybatis自动将java对象映射至sql语句。
+* 4、对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
+
+ * 解决:Mybatis自动将sql执行结果映射至java对象。
+
+### MyBatis和Hibernate的适用场景?
+
+* MyBatis专注于SQL本身,是一个足够灵活的DAO层解决方案。
+* 对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis将是不错的选择。
+
+**开发难易程度和学习成本**
+
+* Hibernate 是重量级框架,学习使用门槛高,适合于需求相对稳定,中小型的项目,比如:办公自动化系统
+
+* MyBatis 是轻量级框架,学习使用门槛低,适合于需求变化频繁,大型的项目,比如:互联网电子商务系统
+
+**总结**
+
+* MyBatis 是一个小巧、方便、高效、简单、直接、半自动化的持久层框架,
+
+* Hibernate 是一个强大、方便、高效、复杂、间接、全自动化的持久层框架。
+
+## MyBatis的架构
+
+### MyBatis编程步骤是什么样的?
+
+* 1、 创建SqlSessionFactory
+
+* 2、 通过SqlSessionFactory创建SqlSession
+
+* 3、 通过sqlsession执行数据库操作
+
+* 4、 调用session.commit()提交事务
+
+* 5、 调用session.close()关闭会话
+
+### 请说说MyBatis的工作原理
+
+* 在学习 MyBatis 程序之前,需要了解一下 MyBatis 工作原理,以便于理解程序。MyBatis 的工作原理如下图 
+
+1. 读取 MyBatis 配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。
+2. 加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。
+3. 构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。
+4. 创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。
+5. Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。
+6. MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。
+7. 输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。
+8. 输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。
+
+### MyBatis的功能架构是怎样的
+
+
+
+* 我们把Mybatis的功能架构分为三层:
+ * API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
+ * 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
+ * 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。
+
+### MyBatis的框架架构设计是怎么样的
+
+
+
+* 这张图从上往下看。MyBatis的初始化,会从mybatis-config.xml配置文件,解析构造成Configuration这个类,就是图中的红框。
+
+1. 加载配置:配置来源于两个地方,一处是配置文件,一处是Java代码的注解,将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中。
+
+2. SQL解析:当API接口层接收到调用请求时,会接收到传入SQL的ID和传入对象(可以是Map、JavaBean或者基本数据类型),Mybatis会根据SQL的ID找到对应的MappedStatement,然后根据传入参数对象对MappedStatement进行解析,解析后可以得到最终要执行的SQL语句和参数。
+
+3. SQL执行:将最终得到的SQL和参数拿到数据库进行执行,得到操作数据库的结果。
+
+4. 结果映射:将操作数据库的结果按照映射的配置进行转换,可以转换成HashMap、JavaBean或者基本数据类型,并将最终结果返回。
+
+### 什么是DBMS
+
+* DBMS:数据库管理系统(database management system)是一种操纵和管理数据库的大型软件,用于建立、使用和维护数zd据库,简称dbms。它对数据库进行统一的管理和控制,以保证数据库的安全性和完整性。用户通过dbms访问数据库中的数据,数据库管理员也通过dbms进行数据库的维护工作。它可使多个应用程序和用户用不同的方法在同时版或不同时刻去建立,修改和询问数据库。DBMS提供数据定义语言[DDL](https://link.juejin.cn?target=https%3A%2F%2Fwww.baidu.com%2Fs%3Fwd%3DDDL%26tn%3DSE_PcZhidaonwhc_ngpagmjz%26rsv_dl%3Dgh_pc_zhidao "https://www.baidu.com/s?wd=DDL&tn=SE_PcZhidaonwhc_ngpagmjz&rsv_dl=gh_pc_zhidao")(Data Definition Language)与数据操作语言[DML](https://link.juejin.cn?target=https%3A%2F%2Fwww.baidu.com%2Fs%3Fwd%3DDML%26tn%3DSE_PcZhidaonwhc_ngpagmjz%26rsv_dl%3Dgh_pc_zhidao "https://www.baidu.com/s?wd=DML&tn=SE_PcZhidaonwhc_ngpagmjz&rsv_dl=gh_pc_zhidao")(Data Manipulation Language),供用户定义数据库的模式结构与权限约束,实现对数据的追加权、删除等操作。
+
+### 为什么需要预编译
+
+* 定义:
+
+ * SQL 预编译指的是数据库驱动在发送 SQL 语句和参数给 DBMS 之前对 SQL 语句进行编译,这样 DBMS 执行 SQL 时,就不需要重新编译。
+* 为什么需要预编译
+
+ * JDBC 中使用对象 PreparedStatement 来抽象预编译语句,使用预编译。预编译阶段可以优化 SQL 的执行。预编译之后的 SQL 多数情况下可以直接执行,DBMS 不需要再次编译,越复杂的SQL,编译的复杂度将越大,预编译阶段可以合并多次操作为一个操作。同时预编译语句对象可以重复利用。把一个 SQL 预编译后产生的 PreparedStatement 对象缓存下来,下次对于同一个SQL,可以直接使用这个缓存的 PreparedState 对象。Mybatis默认情况下,将对所有的 SQL 进行预编译。
+ * 还有一个重要的原因,复制SQL注入
+
+### Mybatis都有哪些Executor执行器?它们之间的区别是什么?
+
+* Mybatis有三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。
+
+* **SimpleExecutor**:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
+
+* **ReuseExecutor**:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。简言之,就是重复使用Statement对象。
+
+* **BatchExecutor**:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。
+
+`作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。`
+
+### Mybatis中如何指定使用哪一种Executor执行器?
+
+* 在Mybatis配置文件中,在设置(settings)可以指定默认的ExecutorType执行器类型,也可以手动给DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数,如SqlSession openSession(ExecutorType execType)。
+
+* 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。
+
+### Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
+
+* Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。
+
+* 它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。
+
+* 当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。
+
+## 映射器
+
+### #{}和${}的区别
+
+* #{}是占位符,预编译处理;${}是拼接符,字符串替换,没有预编译处理。
+
+* Mybatis在处理#{}时,#{}传入参数是以字符串传入,会将SQL中的#{}替换为?号,调用PreparedStatement的set方法来赋值。
+
+* #{} 可以有效的防止SQL注入,提高系统安全性;${} 不能防止SQL 注入
+
+* #{} 的变量替换是在DBMS 中;${} 的变量替换是在 DBMS 外
+
+### 模糊查询like语句该怎么写
+
+* 1 ’%${question}%’ 可能引起SQL注入,不推荐
+* 2 "%"#{question}"%" 注意:因为#{…}解析成sql语句时候,会在变量外侧自动加单引号’ ',所以这里 % 需要使用双引号" ",不能使用单引号 ’ ',不然会查不到任何结果。
+* 3 CONCAT(’%’,#{question},’%’) 使用CONCAT()函数,(推荐)
+* 4 使用bind标签(不推荐)
+
+
+复制代码
+### 在mapper中如何传递多个参数
+
+**方法1:顺序传参法**
+
+public User selectUser(String name, int deptId);
+
+
+复制代码
+
+* #{}里面的数字代表传入参数的顺序。
+
+* 这种方法不建议使用,sql层表达不直观,且一旦顺序调整容易出错。
+
+**方法2:@Param注解传参法**
+
+public User selectUser(@Param("userName") String name, int @Param("deptId") deptId);
+
+
+复制代码
+
+* #{}里面的名称对应的是注解@Param括号里面修饰的名称。
+
+* 这种方法在参数不多的情况还是比较直观的,(推荐使用)。
+
+**方法3:Map传参法**
+
+public User selectUser(Map params);
+
+
+复制代码
+
+* #{}里面的名称对应的是Map里面的key名称。
+
+* 这种方法适合传递多个参数,且参数易变能灵活传递的情况。(推荐使用)。
+
+**方法4:Java Bean传参法**
+
+public User selectUser(User user);
+
+
+复制代码
+
+* #{}里面的名称对应的是User类里面的成员属性。
+
+* 这种方法直观,需要建一个实体类,扩展不容易,需要加属性,但代码可读性强,业务逻辑处理方便,推荐使用。(推荐使用)。
+
+### Mybatis如何执行批量操作
+
+* **使用foreach标签**
+* foreach的主要用在构建in条件中,它可以在SQL语句中进行迭代一个集合。foreach标签的属性主要有item,index,collection,open,separator,close。
+ * item 表示集合中每一个元素进行迭代时的别名,随便起的变量名;
+ * index 指定一个名字,用于表示在迭代过程中,每次迭代到的位置,不常用;
+ * open 表示该语句以什么开始,常用“(”;
+ * separator 表示在每次进行迭代之间以什么符号作为分隔符,常用“,”;
+ * close 表示以什么结束,常用“)”。
+* 在使用foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的,主要有一下3种情况:
+ 1. 如果传入的是单参数且参数类型是一个List的时候,collection属性值为list
+ 2. 如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array
+ 3. 如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了,当然单参数也可以封装成map,实际上如果你在传入参数的时候,在MyBatis里面也是会把它封装成一个Map的,
+ map的key就是参数名,所以这个时候collection属性值就是传入的List或array对象在自己封装的map里面的key
+* 具体用法如下:
+
+
+ //推荐使用
+
+
+ INSERT INTO emp(ename,gender,email,did)
+ VALUES
+
+ (#{emp.eName},#{emp.gender},#{emp.email},#{emp.dept.id})
+
+
+复制代码
+
+
+
+ INSERT INTO emp(ename,gender,email,did)
+ VALUES(#{emp.eName},#{emp.gender},#{emp.email},#{emp.dept.id})
+
+
+复制代码
+
+* **使用ExecutorType.BATCH**
+
+ * Mybatis内置的ExecutorType有3种,默认为simple,该模式下它为每个语句的执行创建一个新的预处理语句,单条提交sql;而batch模式重复使用已经预处理的语句,并且批量执行所有更新语句,显然batch性能将更优; 但batch模式也有自己的问题,比如在Insert操作时,在事务没有提交之前,是没有办法获取到自增的id,这在某型情形下是不符合业务要求的
+
+ * 具体用法如下:
+
+ //批量保存方法测试
+ @Test
+ public void testBatch() throws IOException{
+ SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
+ //可以执行批量操作的sqlSession
+ SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
+
+ //批量保存执行前时间
+ long start = System.currentTimeMillis();
+ try {
+ EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
+ for (int i = 0; i < 1000; i++) {
+ mapper.addEmp(new Employee(UUID.randomUUID().toString().substring(0, 5), "b", "1"));
+ }
+
+ openSession.commit();
+ long end = System.currentTimeMillis();
+ //批量保存执行后的时间
+ System.out.println("执行时长" + (end - start));
+ //批量 预编译sql一次==》设置参数==》10000次==》执行1次 677
+ //非批量 (预编译=设置参数=执行 )==》10000次 1121
+
+ } finally {
+ openSession.close();
+ }
+ }
+ 复制代码
+ * mapper和mapper.xml如下
+
+ public interface EmployeeMapper {
+ //批量保存员工
+ Long addEmp(Employee employee);
+ }
+
+ 复制代码
+
+ insert into employee(lastName,email,gender)
+ values(#{lastName},#{email},#{gender})
+
+
+
+ 复制代码
+
+### 如何获取生成的主键
+
+* 新增标签中添加:keyProperty=" ID " 即可
+
+
+ insert into user(
+ user_name, user_password, create_time)
+ values(#{userName}, #{userPassword} , #{createTime, jdbcType= TIMESTAMP})
+
+
+ 复制代码
+
+
+### 当实体类中的属性名和表中的字段名不一样 ,怎么办
+
+* 第1种: 通过在查询的SQL语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
+
+
+
+ 复制代码
+* 第2种: 通过``来映射字段名和实体类属性名的一一对应的关系。
+
+
+
+
+
+
+
+
+
+
+
+
+ 复制代码
+
+### Mapper 编写有哪几种方式?
+
+* 第一种:接口实现类继承 SqlSessionDaoSupport:使用此种方法需要编写mapper 接口,mapper 接口实现类、mapper.xml 文件。
+
+ 1. 在 sqlMapConfig.xml 中配置 mapper.xml 的位置
+
+
+
+
+
+
+ 复制代码
+ 2. 定义 mapper 接口
+
+ 3. 实现类集成 SqlSessionDaoSupport
+
+ mapper 方法中可以 this.getSqlSession()进行数据增删改查。
+
+ 4. spring 配置
+
+
+
+
+
+ 复制代码
+* 第二种:使用 org.mybatis.spring.mapper.MapperFactoryBean:
+
+ 1. 在 sqlMapConfig.xml 中配置 mapper.xml 的位置,如果 mapper.xml 和mappre 接口的名称相同且在同一个目录,这里可以不用配置
+
+ 2. 定义 mapper 接口:
+
+
+
+
+
+
+ 复制代码
+ 3. mapper.xml 中的 namespace 为 mapper 接口的地址
+
+ 4. mapper 接口中的方法名和 mapper.xml 中的定义的 statement 的 id 保持一致
+
+ 5. Spring 中定义
+
+
+
+
+
+
+ 复制代码
+* 第三种:使用 mapper 扫描器:
+
+ 1. mapper.xml 文件编写:
+
+ mapper.xml 中的 namespace 为 mapper 接口的地址;
+
+ mapper 接口中的方法名和 mapper.xml 中的定义的 statement 的 id 保持一致;
+
+ 如果将 mapper.xml 和 mapper 接口的名称保持一致则不用在 sqlMapConfig.xml中进行配置。
+
+ 2. 定义 mapper 接口:
+
+ 注意 mapper.xml 的文件名和 mapper 的接口名称保持一致,且放在同一个目录
+
+ 3. 配置 mapper 扫描器:
+
+
+
+
+
+
+ 复制代码
+ 4. 使用扫描器后从 spring 容器中获取 mapper 的实现对象。
+
+### 什么是MyBatis的接口绑定?有哪些实现方式?
+
+* 接口绑定,就是在MyBatis中任意定义接口,然后把接口里面的方法和SQL语句绑定,我们直接调用接口方法就可以,这样比起原来了SqlSession提供的方法我们可以有更加灵活的选择和设置。
+
+* 接口绑定有两种实现方式
+
+ 1. 通过注解绑定,就是在接口的方法上面加上 @Select、@Update等注解,里面包含Sql语句来绑定;
+
+ 2. 通过xml里面写SQL来绑定, 在这种情况下,要指定xml映射文件里面的namespace必须为接口的全路径名。当Sql语句比较简单时候,用注解绑定, 当SQL语句比较复杂时候,用xml绑定,一般用xml绑定的比较多。
+
+### 使用MyBatis的mapper接口调用时有哪些要求?
+
+1. Mapper接口方法名和mapper.xml中定义的每个sql的id相同。
+2. Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同。
+3. Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同。
+4. Mapper.xml文件中的namespace即是mapper接口的类路径。
+
+### 这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗
+
+* Dao接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。
+
+* Dao接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略。
+
+### Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?
+
+* 不同的Xml映射文件,如果配置了namespace,那么id可以重复;如果没有配置namespace,那么id不能重复;毕竟namespace不是必须的,只是最佳实践而已。
+
+* 原因就是namespace+id是作为Map的key使用的,如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖。有了namespace,自然id就可以重复,namespace不同,namespace+id自然也就不同。
+
+### 简述Mybatis的Xml映射文件和Mybatis内部数据结构之间的映射关系?
+
+* 答:Mybatis将所有Xml配置信息都封装到All-In-One重量级对象Configuration内部。在Xml映射文件中,``标签会被解析为ParameterMap对象,其每个子元素会被解析为ParameterMapping对象。``标签会被解析为ResultMap对象,其每个子元素会被解析为ResultMapping对象。每一个`