From 7a16bb8763bb7f3b31528aa4277987c13f5b6a01 Mon Sep 17 00:00:00 2001 From: Aaron Ge <525032143@qq.com> Date: Sun, 22 Aug 2021 21:04:01 +0800 Subject: [PATCH 01/19] =?UTF-8?q?Update=20=E4=BB=A3=E7=90=86=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E8=AF=A6=E8=A7=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改文本内容"只能只能"为"只能" --- ...\220\206\346\250\241\345\274\217\350\257\246\350\247\243.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/docs/java/basis/\344\273\243\347\220\206\346\250\241\345\274\217\350\257\246\350\247\243.md" "b/docs/java/basis/\344\273\243\347\220\206\346\250\241\345\274\217\350\257\246\350\247\243.md" index 704de492bef..868bc012e88 100644 --- "a/docs/java/basis/\344\273\243\347\220\206\346\250\241\345\274\217\350\257\246\350\247\243.md" +++ "b/docs/java/basis/\344\273\243\347\220\206\346\250\241\345\274\217\350\257\246\350\247\243.md" @@ -405,7 +405,7 @@ after method send ### 3.3. JDK 动态代理和 CGLIB 动态代理对比 -1. **JDK 动态代理只能只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。** 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。 +1. **JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。** 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。 2. 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。 ## 4. 静态代理和动态代理的对比 From fa7ca138ffd7336e86b36c5ba7241c8f23ebad5e Mon Sep 17 00:00:00 2001 From: anaer Date: Mon, 23 Aug 2021 14:23:34 +0800 Subject: [PATCH 02/19] =?UTF-8?q?Update=20java=E6=96=B0=E7=89=B9=E6=80=A7?= =?UTF-8?q?=E6=80=BB=E7=BB=93.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...26\260\347\211\271\346\200\247\346\200\273\347\273\223.md" | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git "a/docs/java/new-features/java\346\226\260\347\211\271\346\200\247\346\200\273\347\273\223.md" "b/docs/java/new-features/java\346\226\260\347\211\271\346\200\247\346\200\273\347\273\223.md" index c815fa1ab7d..0cbbd1da825 100644 --- "a/docs/java/new-features/java\346\226\260\347\211\271\346\200\247\346\200\273\347\273\223.md" +++ "b/docs/java/new-features/java\346\226\260\347\211\271\346\200\247\346\200\273\347\273\223.md" @@ -60,7 +60,7 @@ jshell 是 Java 9 新增的一个实用工具。为 Java 提供了类似于 Pyth ```java List.of("Java", "C++"); Set.of("Java", "C++"); -Map.of("Java", 1, "C++", 2)`; +Map.of("Java", 1, "C++", 2); ``` 使用 `of()` 创建的集合为不可变集合,不能进行添加、删除、替换、 排序等操作,不然会报 `java.lang.UnsupportedOperationException` 异常。 @@ -945,4 +945,4 @@ Java 15 并没有对此特性进行调整,继续预览特性,主要用于接 - Oracle Java14 record - java14-features - Java 14 Features : -- What is new in Java 15: https://mkyong.com/java/what-is-new-in-java-15/ \ No newline at end of file +- What is new in Java 15: https://mkyong.com/java/what-is-new-in-java-15/ From e8cbc062cedf67163f931e08988f5d1e46130d92 Mon Sep 17 00:00:00 2001 From: anaer Date: Mon, 23 Aug 2021 15:31:11 +0800 Subject: [PATCH 03/19] =?UTF-8?q?Update=20java=E6=96=B0=E7=89=B9=E6=80=A7?= =?UTF-8?q?=E6=80=BB=E7=BB=93.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...6\260\347\211\271\346\200\247\346\200\273\347\273\223.md" | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git "a/docs/java/new-features/java\346\226\260\347\211\271\346\200\247\346\200\273\347\273\223.md" "b/docs/java/new-features/java\346\226\260\347\211\271\346\200\247\346\200\273\347\273\223.md" index 0cbbd1da825..ce6aeabbf71 100644 --- "a/docs/java/new-features/java\346\226\260\347\211\271\346\200\247\346\200\273\347\273\223.md" +++ "b/docs/java/new-features/java\346\226\260\347\211\271\346\200\247\346\200\273\347\273\223.md" @@ -345,8 +345,7 @@ ZGC 主要为了满足如下目标进行设计: - GC 停顿时间不超过 10ms - 即能处理几百 MB 的小堆,也能处理几个 TB 的大堆 - 应用吞吐能力不会下降超过 15%(与 G1 回收算法相比) -- 方便在此基础上引入新的 GC 特性和利用 colord -- 针以及 Load barriers 优化奠定基础 +- 方便在此基础上引入新的 GC 特性和利用 colored 针以及 Load barriers 优化奠定基础 - 当前只支持 Linux/x64 位平台 ZGC 目前 **处在实验阶段**,只支持 Linux/x64 平台。 @@ -725,7 +724,7 @@ System.out.println(result); 简化数据类的定义方式,使用 `record` 代替 `class` 定义的类,只需要声明属性,就可以在获得属性的访问方法,以及 `toString()`,`hashCode()`, `equals()`方法 -类似于使用 `class` 定义类,同时使用了 lomobok 插件,并打上了`@Getter,@ToString,@EqualsAndHashCode`注解 +类似于使用 `class` 定义类,同时使用了 lombok 插件,并打上了`@Getter,@ToString,@EqualsAndHashCode`注解 ```java /** From 40a7fcb76611ca35f7dcd59866b4146a8adc1bd0 Mon Sep 17 00:00:00 2001 From: anaer Date: Mon, 23 Aug 2021 15:46:19 +0800 Subject: [PATCH 04/19] =?UTF-8?q?Update=20java=E6=96=B0=E7=89=B9=E6=80=A7?= =?UTF-8?q?=E6=80=BB=E7=BB=93.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\226\260\347\211\271\346\200\247\346\200\273\347\273\223.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/docs/java/new-features/java\346\226\260\347\211\271\346\200\247\346\200\273\347\273\223.md" "b/docs/java/new-features/java\346\226\260\347\211\271\346\200\247\346\200\273\347\273\223.md" index ce6aeabbf71..e0c42836d88 100644 --- "a/docs/java/new-features/java\346\226\260\347\211\271\346\200\247\346\200\273\347\273\223.md" +++ "b/docs/java/new-features/java\346\226\260\347\211\271\346\200\247\346\200\273\347\273\223.md" @@ -205,7 +205,7 @@ public interface MyInterface { - 类 `CompletableFuture` 中增加了几个新的方法(`completeAsync` ,`orTimeout` 等) - **Nashorn 引擎的增强** :Nashorn 从 Java8 开始引入的 JavaScript 引擎,Java9 对 Nashorn 做了些增强,实现了一些 ES6 的新特性(Java 11 中已经被弃用)。 - **I/O 流的新特性** :增加了新的方法来读取和复制 `InputStream` 中包含的数据 -- **改进应用的安全性能** :Java 9 新增了 4 个 SHA- 3 哈希算法,SHA3-224、SHA3-256、SHA3-384 和 S HA3-512 +- **改进应用的安全性能** :Java 9 新增了 4 个 SHA- 3 哈希算法,SHA3-224、SHA3-256、SHA3-384 和 SHA3-512 - ...... ## Java10 From 46ad9b44e0ada47a287c273f9b5a4452aec8ea89 Mon Sep 17 00:00:00 2001 From: lllidany <41785993+lllidany@users.noreply.github.com> Date: Mon, 23 Aug 2021 19:06:28 +0800 Subject: [PATCH 05/19] Update linux.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 3.3里 改字符设备”硬盘“为”键盘“ hh --- docs/cs-basics/operating-system/linux.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cs-basics/operating-system/linux.md b/docs/cs-basics/operating-system/linux.md index 80b52fba951..87d4f9370eb 100644 --- a/docs/cs-basics/operating-system/linux.md +++ b/docs/cs-basics/operating-system/linux.md @@ -224,7 +224,7 @@ Linux 支持很多文件类型,其中非常重要的文件类型有: **普通 - **普通文件(-)** : 用于存储信息和数据, Linux 用户可以根据访问权限对普通文件进行查看、更改和删除。比如:图片、声音、PDF、text、视频、源代码等等。 - **目录文件(d,directory file)** :目录也是文件的一种,用于表示和管理系统中的文件,目录文件中包含一些文件名和子目录名。打开目录事实上就是打开目录文件。 - **符号链接文件(l,symbolic link)** :保留了指向文件的地址而不是文件本身。 -- **字符设备(c,char)** :用来访问字符设备比如硬盘。 +- **字符设备(c,char)** :用来访问字符设备比如键盘。 - **设备文件(b,block)** : 用来访问块设备比如硬盘、软盘。 - **管道文件(p,pipe)** : 一种特殊类型的文件,用于进程之间的通信。 - **套接字(s,socket)** :用于进程间的网络通信,也可以用于本机之间的非网络通信。 From d7356a07cbc52263b017e53ba4cdcd8f0fe173ee Mon Sep 17 00:00:00 2001 From: Snoopy Date: Tue, 24 Aug 2021 08:11:10 +0800 Subject: [PATCH 06/19] =?UTF-8?q?Update=20Java=E5=9F=BA=E7=A1=80=E7=9F=A5?= =?UTF-8?q?=E8=AF=86.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修正拼写错误 --- .../Java\345\237\272\347\241\200\347\237\245\350\257\206.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" index 7c03a11179d..88bead11e1a 100644 --- "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" @@ -1408,7 +1408,7 @@ String s = input.readLine(); - 按照操作单元划分,可以划分为字节流和字符流; - 按照流的角色划分为节点流和处理流。 -Java Io 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。 +Java IO 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。 - InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。 - OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。 From c58b5a2525121054aae0dbe403fda1d9448729a6 Mon Sep 17 00:00:00 2001 From: anaer Date: Tue, 24 Aug 2021 10:37:53 +0800 Subject: [PATCH 07/19] Update Shell.md typo --- docs/cs-basics/operating-system/Shell.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/cs-basics/operating-system/Shell.md b/docs/cs-basics/operating-system/Shell.md index 099bcaf6678..ce54f0e8aaa 100644 --- a/docs/cs-basics/operating-system/Shell.md +++ b/docs/cs-basics/operating-system/Shell.md @@ -57,7 +57,7 @@ W3Cschool 上的一篇文章是这样介绍 Shell的,如下图所示。 ### Shell 编程的 Hello World -学习任何一门编程语言第一件事就是输出HelloWord了!下面我会从新建文件到shell代码编写来说下Shell 编程如何输出Hello World。 +学习任何一门编程语言第一件事就是输出HelloWorld了!下面我会从新建文件到shell代码编写来说下Shell 编程如何输出Hello World。 (1)新建一个文件 helloworld.sh :`touch helloworld.sh`,扩展名为 sh(sh代表Shell)(扩展名并不影响脚本执行,见名知意就好,如果你用 php 写 shell 脚本,扩展名就用 php 好了) @@ -94,14 +94,14 @@ shell中 # 符号表示注释。**shell 的第一行比较特殊,一般都会 3. **Shell变量** :Shell变量是由 Shell 程序设置的特殊变量。Shell 变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了 Shell 的正常运行 **常用的环境变量:** -> PATH 决定了shell将到哪些目录中寻找命令或程序 -HOME 当前用户主目录 -HISTSIZE 历史记录数 -LOGNAME 当前用户的登录名 -HOSTNAME 指主机的名称 -SHELL 当前用户Shell类型 -LANGUGE  语言相关的环境变量,多语言可以修改此环境变量 -MAIL 当前用户的邮件存放目录 +> PATH 决定了shell将到哪些目录中寻找命令或程序 +HOME 当前用户主目录 +HISTSIZE 历史记录数 +LOGNAME 当前用户的登录名 +HOSTNAME 指主机的名称 +SHELL 当前用户Shell类型 +LANGUGE  语言相关的环境变量,多语言可以修改此环境变量 +MAIL 当前用户的邮件存放目录 PS1 基本提示符,对于root用户是#,对于普通用户是$ **使用 Linux 已定义的环境变量:** From 39836b0d9a00421917db696835b9287f435e9a40 Mon Sep 17 00:00:00 2001 From: anaer Date: Tue, 24 Aug 2021 10:43:33 +0800 Subject: [PATCH 08/19] Update Shell.md --- docs/cs-basics/operating-system/Shell.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cs-basics/operating-system/Shell.md b/docs/cs-basics/operating-system/Shell.md index ce54f0e8aaa..0a7c15080af 100644 --- a/docs/cs-basics/operating-system/Shell.md +++ b/docs/cs-basics/operating-system/Shell.md @@ -100,7 +100,7 @@ HISTSIZE 历史记录数 LOGNAME 当前用户的登录名 HOSTNAME 指主机的名称 SHELL 当前用户Shell类型 -LANGUGE  语言相关的环境变量,多语言可以修改此环境变量 +LANGUAGE  语言相关的环境变量,多语言可以修改此环境变量 MAIL 当前用户的邮件存放目录 PS1 基本提示符,对于root用户是#,对于普通用户是$ From d8cae225956aece69d64f5ae28e910df4834e66c Mon Sep 17 00:00:00 2001 From: anaer Date: Tue, 24 Aug 2021 10:58:24 +0800 Subject: [PATCH 09/19] Update naming.md --- docs/system-design/naming.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/system-design/naming.md b/docs/system-design/naming.md index 33579c9160c..f6e1dd59396 100644 --- a/docs/system-design/naming.md +++ b/docs/system-design/naming.md @@ -69,7 +69,7 @@ 驼峰命名法应该我们最常见的一个,这种命名方式使用大小写混合的格式来区别各个单词,并且单词之间不使用空格隔开或者连接字符连接的命名方式 -#### 大驼峰命名法(CamelCase) +#### 大驼峰命名法(UpperCamelCase) **类名需要使用大驼峰命名法(UpperCamelCase)** From b55ee4a63420d0c025fe0c4dfb75f6d9cdca5493 Mon Sep 17 00:00:00 2001 From: anaer Date: Tue, 24 Aug 2021 11:28:43 +0800 Subject: [PATCH 10/19] =?UTF-8?q?Update=20Spring=E5=B8=B8=E8=A7=81?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E6=80=BB=E7=BB=93.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...56\351\242\230\346\200\273\347\273\223.md" | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git "a/docs/system-design/framework/spring/Spring\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" "b/docs/system-design/framework/spring/Spring\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" index 529406be945..cf8ffffa00c 100644 --- "a/docs/system-design/framework/spring/Spring\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" +++ "b/docs/system-design/framework/spring/Spring\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" @@ -94,9 +94,9 @@ Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉 ### 谈谈自己对于 AOP 的了解 -AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码**,**降低模块间的耦合度,并有利于未来的可拓展性和可维护性。 +AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。 -Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 **JDK Proxy**,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 **Cglib** ,这时候 Spring AOP 会使用 **Cglib** 生成一个被代理对象的子类来作为代理,如下图所示: +Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 **JDK Proxy**,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 **Cglib** 生成一个被代理对象的子类来作为代理,如下图所示: ![SpringAOPProcess](https://images.xiaozhuanlan.com/photo/2019/926dfc549b06d280a37397f9fd49bf9d.jpg) @@ -116,7 +116,7 @@ Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系 简单来说,bean 代指的就是那些被 IoC 容器所管理的对象。 -我们需要告诉 IoC 容器帮助我们管理哪些对象,这个是通过配置元数据的定义的。配置元数据可以是 XML 文件、注解或者 Java 配置类。 +我们需要告诉 IoC 容器帮助我们管理哪些对象,这个是通过配置元数据来定义的。配置元数据可以是 XML 文件、注解或者 Java 配置类。 ```xml @@ -133,7 +133,7 @@ Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系 ### bean 的作用域有哪些? -Spring 中 Bean 的作用域通常由下面几种: +Spring 中 Bean 的作用域通常有下面几种: - **singleton** : 唯一 bean 实例,Spring 中的 bean 默认都是单例的,对单例设计模式的应用。 - **prototype** : 每次请求都会创建一个新的 bean 实例。 @@ -173,8 +173,8 @@ public Person personPrototype() { ### @Component 和 @Bean 的区别是什么? 1. `@Component` 注解作用于类,而`@Bean`注解作用于方法。 -2. `@Component`通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用 `@ComponentScan` 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。`@Bean` 注解通常是我们在标有该注解的方法中定义产生这个 bean,`@Bean`告诉了 Spring 这是某个类的示例,当我需要用它的时候还给我。 -3. `@Bean` 注解比 `Component` 注解的自定义性更强,而且很多地方我们只能通过 `@Bean` 注解来注册 bean。比如当我们引用第三方库中的类需要装配到 `Spring`容器时,则只能通过 `@Bean`来实现。 +2. `@Component`通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用 `@ComponentScan` 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。`@Bean` 注解通常是我们在标有该注解的方法中定义产生这个 bean,`@Bean`告诉了 Spring 这是某个类的实例,当我需要用它的时候还给我。 +3. `@Bean` 注解比 `@Component` 注解的自定义性更强,而且很多地方我们只能通过 `@Bean` 注解来注册 bean。比如当我们引用第三方库中的类需要装配到 `Spring`容器时,则只能通过 `@Bean`来实现。 `@Bean`注解使用示例: @@ -407,16 +407,16 @@ public enum Isolation { 当 `@Transactional` 注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。 -在 `@Transactional` 注解中如果不配置`rollbackFor`属性,那么事物只会在遇到`RuntimeException`的时候才会回滚,加上 `rollbackFor=Exception.class`,可以让事物在遇到非运行时异常时也回滚。 +在 `@Transactional` 注解中如果不配置`rollbackFor`属性,那么事务只会在遇到`RuntimeException`的时候才会回滚,加上 `rollbackFor=Exception.class`,可以让事务在遇到非运行时异常时也回滚。 ## JPA ### 如何使用 JPA 在数据库中非持久化一个字段? -假如我们有有下面一个类: +假如我们有下面一个类: ```java -Entity(name="USER") +@Entity(name="USER") public class User { @Id @@ -439,7 +439,7 @@ public class User { ```java static String transient1; // not persistent because of static -final String transient2 = “Satish”; // not persistent because of final +final String transient2 = "Satish"; // not persistent because of final transient String transient3; // not persistent because of transient @Transient String transient4; // not persistent because of @Transient From e69859b1cbb27ba535d8041a814d2dcb81e97f6c Mon Sep 17 00:00:00 2001 From: guide Date: Tue, 24 Aug 2021 17:44:13 +0800 Subject: [PATCH 11/19] =?UTF-8?q?[feat]=E4=BF=AE=E5=A4=8D=E9=93=BE?= =?UTF-8?q?=E6=8E=A5=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../MySQL\344\270\211\345\244\247\346\227\245\345\277\227.md" | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename "docs/database/mysql/MySQL\344\270\211\345\244\247\346\227\245\345\277\227(binlog\343\200\201redo log\345\222\214undo log).md" => "docs/database/mysql/MySQL\344\270\211\345\244\247\346\227\245\345\277\227.md" (100%) diff --git a/README.md b/README.md index 786e0ccf6d9..2235b6e7874 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,7 @@ JVM 这部分内容主要参考 [JVM 虚拟机规范-Java8 ](https://docs.oracle 1. [MySQL数据库索引总结](docs/database/mysql/MySQL数据库索引.md) 2. [事务隔离级别(图文详解)](docs/database/mysql/事务隔离级别(图文详解).md) -3. [MySQL三大日志(binlog、redo log和undo log)详解](docs/database/mysql/MySQL三大日志(binlog、redo log和undo log).md) +3. [MySQL三大日志(binlog、redo log和undo log)详解](docs/database/mysql/MySQL三大日志.md) 4. [InnoDB存储引擎对MVCC的实现](docs/database/mysql/InnoDB对MVCC的实现.md) 5. [一条 SQL 语句在 MySQL 中如何执行的](docs/database/mysql/一条sql语句在mysql中如何执行的.md) 6. [关于数据库中如何存储时间的一点思考](docs/database/mysql/关于数据库存储时间的一点思考.md) diff --git "a/docs/database/mysql/MySQL\344\270\211\345\244\247\346\227\245\345\277\227(binlog\343\200\201redo log\345\222\214undo log).md" "b/docs/database/mysql/MySQL\344\270\211\345\244\247\346\227\245\345\277\227.md" similarity index 100% rename from "docs/database/mysql/MySQL\344\270\211\345\244\247\346\227\245\345\277\227(binlog\343\200\201redo log\345\222\214undo log).md" rename to "docs/database/mysql/MySQL\344\270\211\345\244\247\346\227\245\345\277\227.md" From 64fb03bf9c10804b6ee7b723d63130ad88088163 Mon Sep 17 00:00:00 2001 From: anaer Date: Wed, 25 Aug 2021 17:32:35 +0800 Subject: [PATCH 12/19] Update java8-common-new-features.md typo --- docs/java/new-features/java8-common-new-features.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/java/new-features/java8-common-new-features.md b/docs/java/new-features/java8-common-new-features.md index f0630eff520..c518a149683 100644 --- a/docs/java/new-features/java8-common-new-features.md +++ b/docs/java/new-features/java8-common-new-features.md @@ -78,7 +78,7 @@ public class InterfaceNewImpl implements InterfaceNew , InterfaceNew1{ 2. interface 的方法是更像是一个扩展插件。而 abstract class 的方法是要继承的。 -开始我们也提到,interface 新增`default`,和`static`修饰的方法,为了解决接口的修改与现有的实现不兼容的问题,并不是为了要替代`abstract class`。在使用上,该用 abstract class 的地方还是要用 abstract class,不要因为 interface 的新特性而降之替换。 +开始我们也提到,interface 新增`default`和`static`修饰的方法,为了解决接口的修改与现有的实现不兼容的问题,并不是为了要替代`abstract class`。在使用上,该用 abstract class 的地方还是要用 abstract class,不要因为 interface 的新特性而将之替换。 **记住接口永远和类不一样。** @@ -463,7 +463,7 @@ Predicate.test 执行 按执行顺序应该是先打印 4 次「`Predicate.test` 执行」,再打印「`count` 执行」。实际结果恰恰相反。说明 filter 中的方法并没有立刻执行,而是等调用`count()`方法后才执行。 -上面都是串行 `Stream` 的实例。并行 `parallelStream` 在使用方法上和串行一样。主要区别是 `parallelStream` 可多线程执行,是基于 ForkJoin 框架实现的,有时间大家可以了解一下 `ForkJoin` 框架和 `ForkJoinPool`。这里可以简单的理解它是通过线程池来实现的,这样就会涉及到线程安全,线程消耗等问题。下面我们通过代码来体验一下串行流的多线程执行。 +上面都是串行 `Stream` 的实例。并行 `parallelStream` 在使用方法上和串行一样。主要区别是 `parallelStream` 可多线程执行,是基于 ForkJoin 框架实现的,有时间大家可以了解一下 `ForkJoin` 框架和 `ForkJoinPool`。这里可以简单的理解它是通过线程池来实现的,这样就会涉及到线程安全,线程消耗等问题。下面我们通过代码来体验一下并行流的多线程执行。 ```java @Test @@ -733,7 +733,7 @@ public Optional filter(Predicate predicate) { ### 小结 -看完 `Optional` 源码,`Optional` 的方法真的非常简单,值得注意的是如果坚决不想看见 `NPE`,就不要用 `of() `、 `get()` 、`flatMap(..)`\。最后再综合用一下 `Optional` 的高频方法。 +看完 `Optional` 源码,`Optional` 的方法真的非常简单,值得注意的是如果坚决不想看见 `NPE`,就不要用 `of() `、 `get()` 、`flatMap(..)`。最后再综合用一下 `Optional` 的高频方法。 ```java Optional.ofNullable(zoo).map(o -> o.getDog()).map(d -> d.getAge()).filter(v->v==1).orElse(3); @@ -947,7 +947,7 @@ public void getDayNew() { 1. `Date` ---> `LocalDate` 2. `Time` ---> `LocalTime` -3. `TimesSamp` ---> `LocalDateTime` +3. `Timestamp` ---> `LocalDateTime` 而之前统统对应 `Date`,也只有 `Date`。 @@ -1017,4 +1017,4 @@ System.out.println("本地时区时间: " + localZoned); - Optional - Date time-api -这些都是开发当中比较常用的特征。梳理下来发现它们真香,而我却没有更早的应用。总觉得学习 java 8 新特性比较麻烦,一致使用老的实现方式。其实这些新特性几天就可以掌握,一但掌握,效率会有很大的提高。其实我们涨工资也是涨的学习的钱,不学习终究会被淘汰,35 岁危机会提前来临。 +这些都是开发当中比较常用的特性。梳理下来发现它们真香,而我却没有更早的应用。总觉得学习 java 8 新特性比较麻烦,一直使用老的实现方式。其实这些新特性几天就可以掌握,一但掌握,效率会有很大的提高。其实我们涨工资也是涨的学习的钱,不学习终究会被淘汰,35 岁危机会提前来临。 From dfda4a4a6a9f1c689077209b6be3175ce7f27876 Mon Sep 17 00:00:00 2001 From: Aaron Ge <525032143@qq.com> Date: Wed, 25 Aug 2021 23:37:22 +0800 Subject: [PATCH 13/19] =?UTF-8?q?Update=20IO=E6=A8=A1=E5=9E=8B.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更新“网络请求和相应”为“网络请求和响应” --- "docs/java/basis/IO\346\250\241\345\236\213.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/docs/java/basis/IO\346\250\241\345\236\213.md" "b/docs/java/basis/IO\346\250\241\345\236\213.md" index 243fdc00107..730e1e24f06 100644 --- "a/docs/java/basis/IO\346\250\241\345\236\213.md" +++ "b/docs/java/basis/IO\346\250\241\345\236\213.md" @@ -36,7 +36,7 @@ I/O(**I**nput/**O**utpu) 即**输入/输出** 。 因此,用户进程想要执行 IO 操作的话,必须通过 **系统调用** 来间接访问内核空间 -我们在平常开发过程中接触最多的就是 **磁盘 IO(读写文件)** 和 **网络 IO(网络请求和相应)**。 +我们在平常开发过程中接触最多的就是 **磁盘 IO(读写文件)** 和 **网络 IO(网络请求和响应)**。 **从应用程序的视角来看的话,我们的应用程序对操作系统的内核发起 IO 调用(系统调用),操作系统负责的内核执行具体的 IO 操作。也就是说,我们的应用程序实际上只是发起了 IO 操作的调用而已,具体 IO 的执行是由操作系统的内核来完成的。** From 413e15cc5ed3ca4e2f3b45162315990fbe45b86f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=9C=A8?= Date: Thu, 26 Aug 2021 10:52:09 +0800 Subject: [PATCH 14/19] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=B8=83=E9=9A=86?= =?UTF-8?q?=E8=BF=87=E6=BB=A4=E5=99=A8=E4=B8=80=E6=96=87=E9=93=BE=E6=8E=A5?= =?UTF-8?q?=E5=9C=B0=E5=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 布隆过滤器链接失效 --- docs/database/Redis/redis-all.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/database/Redis/redis-all.md b/docs/database/Redis/redis-all.md index 93227374d2e..74d0d6b0184 100644 --- a/docs/database/Redis/redis-all.md +++ b/docs/database/Redis/redis-all.md @@ -743,7 +743,7 @@ _为什么会出现误判的情况呢? 我们还要从布隆过滤器的原理 然后,一定会出现这样一种情况:**不同的字符串可能哈希出来的位置相同。** (可以适当增加位数组大小或者调整我们的哈希函数来降低概率) -更多关于布隆过滤器的内容可以看我的这篇原创:[《不了解布隆过滤器?一文给你整的明明白白!》](https://github.com/Snailclimb/JavaGuide/blob/master/docs/dataStructures-algorithms/data-structure/bloom-filter.md) ,强烈推荐,个人感觉网上应该找不到总结的这么明明白白的文章了。 +更多关于布隆过滤器的内容可以看我的这篇原创:[《不了解布隆过滤器?一文给你整的明明白白!》](https://github.com/Snailclimb/JavaGuide/blob/master/docs/cs-basics/data-structure/bloom-filter.md) ,强烈推荐,个人感觉网上应该找不到总结的这么明明白白的文章了。 ### 缓存雪崩 From b5db9e9bdabb93b122761b4b14da24f2174a8915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=9C=A8?= Date: Thu, 26 Aug 2021 11:04:57 +0800 Subject: [PATCH 15/19] =?UTF-8?q?=E8=B7=B3=E8=BD=AC=E9=93=BE=E6=8E=A5?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 链接地址处理 --- docs/cs-basics/data-structure/bloom-filter.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/cs-basics/data-structure/bloom-filter.md b/docs/cs-basics/data-structure/bloom-filter.md index 9b5afd6d5c5..1431e97b1c8 100644 --- a/docs/cs-basics/data-structure/bloom-filter.md +++ b/docs/cs-basics/data-structure/bloom-filter.md @@ -232,7 +232,8 @@ System.out.println(filter.mightContain(2)); Redis v4.0 之后有了 Module(模块/插件) 功能,Redis Modules 让 Redis 可以使用外部模块扩展其功能 。布隆过滤器就是其中的 Module。详情可以查看 Redis 官方对 Redis Modules 的介绍 :https://redis.io/modules -另外,官网推荐了一个 RedisBloom 作为 Redis 布隆过滤器的 Module,地址:https://github.com/RedisBloom/RedisBloom。其他还有: +另外,官网推荐了一个 RedisBloom 作为 Redis 布隆过滤器的 Module,地址:https://github.com/RedisBloom/RedisBloom +其他还有: - redis-lua-scaling-bloom-filter(lua 脚本实现):https://github.com/erikdubbelboer/redis-lua-scaling-bloom-filter - pyreBloom(Python 中的快速 Redis 布隆过滤器) :https://github.com/seomoz/pyreBloom From b552b3fbe47745771e4c50811b11c9d40392e09e Mon Sep 17 00:00:00 2001 From: chengcjk <739009651@qq.com> Date: Thu, 26 Aug 2021 11:05:58 +0800 Subject: [PATCH 16/19] =?UTF-8?q?=E6=A0=A1=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/docs/java/multi-thread/2020\346\234\200\346\226\260Java\345\271\266\345\217\221\350\277\233\351\230\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" "b/docs/java/multi-thread/2020\346\234\200\346\226\260Java\345\271\266\345\217\221\350\277\233\351\230\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" index 365d5aec088..e4e8d19331c 100644 --- "a/docs/java/multi-thread/2020\346\234\200\346\226\260Java\345\271\266\345\217\221\350\277\233\351\230\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" +++ "b/docs/java/multi-thread/2020\346\234\200\346\226\260Java\345\271\266\345\217\221\350\277\233\351\230\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" @@ -616,7 +616,7 @@ public ThreadPoolExecutor(int corePoolSize, **`ThreadPoolExecutor` 饱和策略定义:** -如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任时,`ThreadPoolTaskExecutor` 定义一些策略: +如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任务时,`ThreadPoolTaskExecutor` 定义一些策略: - **`ThreadPoolExecutor.AbortPolicy`:** 抛出 `RejectedExecutionException`来拒绝新任务的处理。 - **`ThreadPoolExecutor.CallerRunsPolicy`:** 调用执行自己的线程运行任务,也就是直接在调用`execute`方法的线程中运行(`run`)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。如果您的应用程序可以承受此延迟并且你要求任何一个任务请求都要被执行的话,你可以选择这个策略。 From f25a32dae090dc0cdeac06ec5024b85163e29e4e Mon Sep 17 00:00:00 2001 From: guide Date: Thu, 26 Aug 2021 11:43:46 +0800 Subject: [PATCH 17/19] =?UTF-8?q?[refractor]=E7=A7=BB=E9=99=A4=E9=9B=86?= =?UTF-8?q?=E5=90=88=E9=83=A8=E5=88=86=E5=86=85=E5=AE=B9=EF=BC=8C=E5=8D=95?= =?UTF-8?q?=E7=8B=AC=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...06\347\226\221\351\232\276\347\202\271.md" | 277 +----------------- 1 file changed, 9 insertions(+), 268 deletions(-) diff --git "a/docs/java/basis/Java\345\237\272\347\241\200\347\237\245\350\257\206\347\226\221\351\232\276\347\202\271.md" "b/docs/java/basis/Java\345\237\272\347\241\200\347\237\245\350\257\206\347\226\221\351\232\276\347\202\271.md" index eb5c17b2c79..a0e008f33dd 100644 --- "a/docs/java/basis/Java\345\237\272\347\241\200\347\237\245\350\257\206\347\226\221\351\232\276\347\202\271.md" +++ "b/docs/java/basis/Java\345\237\272\347\241\200\347\237\245\350\257\206\347\226\221\351\232\276\347\202\271.md" @@ -1,29 +1,4 @@ - - -- [1. 基础](#1-基础) - - [1.1. 正确使用 equals 方法](#11-正确使用-equals-方法) - - [1.2. 整型包装类值的比较](#12-整型包装类值的比较) - - [1.3. BigDecimal](#13-bigdecimal) - - [1.3.1. BigDecimal 的用处](#131-bigdecimal-的用处) - - [1.3.2. BigDecimal 的大小比较](#132-bigdecimal-的大小比较) - - [1.3.3. BigDecimal 保留几位小数](#133-bigdecimal-保留几位小数) - - [1.3.4. BigDecimal 的使用注意事项](#134-bigdecimal-的使用注意事项) - - [1.3.5. 总结](#135-总结) - - [1.4. 基本数据类型与包装数据类型的使用标准](#14-基本数据类型与包装数据类型的使用标准) -- [2. 集合](#_2-集合) - - [2.1. Arrays.asList()使用指南](#21-arraysaslist使用指南) - - [2.1.1. 简介](#211-简介) - - [2.1.2. 《阿里巴巴Java 开发手册》对其的描述](#212-阿里巴巴java-开发手册对其的描述) - - [2.1.3. 使用时的注意事项总结](#213-使用时的注意事项总结) - - [2.1.4. 如何正确的将数组转换为ArrayList?](#214-如何正确的将数组转换为arraylist) - - [2.2. Collection.toArray()方法使用的坑&如何反转数组](#22-collectiontoarray方法使用的坑如何反转数组) - - [2.3. 不要在 foreach 循环里进行元素的 remove/add 操作](#23-不要在-foreach-循环里进行元素的-removeadd-操作) - - - -# 1. 基础 - -## 1.1. 正确使用 equals 方法 +## 正确使用 equals 方法 Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。 @@ -65,7 +40,7 @@ Reference:[Java中equals方法造成空指针异常的原因及解决方案](htt - 可以使用 == 或者 != 操作来比较null值,但是不能使用其他算法或者逻辑操作。在Java中`null == null`将返回true。 - 不能使用一个值为null的引用类型变量来调用非静态方法,否则会抛出异常 -## 1.2. 整型包装类值的比较 +## 整型包装类值的比较 所有整型包装类对象值的比较必须使用equals方法。 @@ -85,9 +60,9 @@ System.out.println(i1==i2);//false **注意:** 如果你的IDE(IDEA/Eclipse)上安装了阿里巴巴的p3c插件,这个插件如果检测到你用 ==的话会报错提示,推荐安装一个这个插件,很不错。 -## 1.3. BigDecimal +## BigDecimal -### 1.3.1. BigDecimal 的用处 +### BigDecimal 的用处 《阿里巴巴Java开发手册》中提到:**浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。** 具体原理和浮点数的编码方式有关,这里就不多提了,我们下面直接上实例: @@ -113,7 +88,7 @@ System.out.println(y); /* 0.1 */ System.out.println(Objects.equals(x, y)); /* true */ ``` -### 1.3.2. BigDecimal 的大小比较 +### BigDecimal 的大小比较 `a.compareTo(b)` : 返回 -1 表示 `a` 小于 `b`,0 表示 `a` 等于 `b` , 1表示 `a` 大于 `b`。 @@ -122,7 +97,7 @@ BigDecimal a = new BigDecimal("1.0"); BigDecimal b = new BigDecimal("0.9"); System.out.println(a.compareTo(b));// 1 ``` -### 1.3.3. BigDecimal 保留几位小数 +### BigDecimal 保留几位小数 通过 `setScale`方法设置保留几位小数以及保留规则。保留规则有挺多种,不需要记,IDEA会提示。 @@ -132,19 +107,19 @@ BigDecimal n = m.setScale(3,BigDecimal.ROUND_HALF_DOWN); System.out.println(n);// 1.255 ``` -### 1.3.4. BigDecimal 的使用注意事项 +### BigDecimal 的使用注意事项 注意:我们在使用BigDecimal时,为了防止精度丢失,推荐使用它的 **BigDecimal(String)** 构造方法来创建对象。《阿里巴巴Java开发手册》对这部分内容也有提到如下图所示。 ![《阿里巴巴Java开发手册》对这部分BigDecimal的描述](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019/7/BigDecimal.png) -### 1.3.5. 总结 +### 总结 BigDecimal 主要用来操作(大)浮点数,BigInteger 主要用来操作大整数(超过 long 类型)。 BigDecimal 的实现利用到了 BigInteger, 所不同的是 BigDecimal 加入了小数位的概念 -## 1.4. 基本数据类型与包装数据类型的使用标准 +## 基本数据类型与包装数据类型的使用标准 Reference:《阿里巴巴Java开发手册》 @@ -160,237 +135,3 @@ Reference:《阿里巴巴Java开发手册》 **反例** : 比如显示成交总额涨跌情况,即正负 x%,x 为基本数据类型,调用的 RPC 服务,调用不成功时,返回的是默认值,页面显示为 0%,这是不合理的,应该显示成中划线。所以包装数据类型的 null 值,能够表示额外的信息,如:远程调用失败,异常退出。 -# 2. 集合 - -## 2.1. Arrays.asList()使用指南 - -最近使用`Arrays.asList()`遇到了一些坑,然后在网上看到这篇文章:[Java Array to List Examples](http://javadevnotes.com/java-array-to-list-examples) 感觉挺不错的,但是还不是特别全面。所以,自己对于这块小知识点进行了简单的总结。 - -### 2.1.1. 简介 - -`Arrays.asList()`在平时开发中还是比较常见的,我们可以使用它将一个数组转换为一个List集合。 - -```java -String[] myArray = {"Apple", "Banana", "Orange"}; -List myList = Arrays.asList(myArray); -//上面两个语句等价于下面一条语句 -List myList = Arrays.asList("Apple","Banana", "Orange"); -``` - -JDK 源码对于这个方法的说明: - -```java -/** - *返回由指定数组支持的固定大小的列表。此方法作为基于数组和基于集合的API之间的桥梁, - * 与 Collection.toArray()结合使用。返回的List是可序列化并实现RandomAccess接口。 - */ -public static List asList(T... a) { - return new ArrayList<>(a); -} -``` - -### 2.1.2. 《阿里巴巴Java 开发手册》对其的描述 - -`Arrays.asList()`将数组转换为集合后,底层其实还是数组,《阿里巴巴Java 开发手册》对于这个方法有如下描述: - -![阿里巴巴Java开发手-Arrays.asList()方法](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/阿里巴巴Java开发手-Arrays.asList()方法.png) - -### 2.1.3. 使用时的注意事项总结 - -**传递的数组必须是对象数组,而不是基本类型。** - -`Arrays.asList()`是泛型方法,传入的对象必须是对象数组。 - -```java -int[] myArray = {1, 2, 3}; -List myList = Arrays.asList(myArray); -System.out.println(myList.size());//1 -System.out.println(myList.get(0));//数组地址值 -System.out.println(myList.get(1));//报错:ArrayIndexOutOfBoundsException -int[] array = (int[]) myList.get(0); -System.out.println(array[0]);//1 -``` -当传入一个原生数据类型数组时,`Arrays.asList()` 的真正得到的参数就不是数组中的元素,而是数组对象本身!此时List 的唯一元素就是这个数组,这也就解释了上面的代码。 - -我们使用包装类型数组就可以解决这个问题。 - -```java -Integer[] myArray = {1, 2, 3}; -``` - -**使用集合的修改方法:`add()`、`remove()`、`clear()`会抛出异常。** - -```java -List myList = Arrays.asList(1, 2, 3); -myList.add(4);//运行时报错:UnsupportedOperationException -myList.remove(1);//运行时报错:UnsupportedOperationException -myList.clear();//运行时报错:UnsupportedOperationException -``` - -`Arrays.asList()` 方法返回的并不是 `java.util.ArrayList` ,而是 `java.util.Arrays` 的一个内部类,这个内部类并没有实现集合的修改方法或者说并没有重写这些方法。 - -```java -List myList = Arrays.asList(1, 2, 3); -System.out.println(myList.getClass());//class java.util.Arrays$ArrayList -``` - -下图是`java.util.Arrays$ArrayList`的简易源码,我们可以看到这个类重写的方法有哪些。 - -```java - private static class ArrayList extends AbstractList - implements RandomAccess, java.io.Serializable - { - ... - - @Override - public E get(int index) { - ... - } - - @Override - public E set(int index, E element) { - ... - } - - @Override - public int indexOf(Object o) { - ... - } - - @Override - public boolean contains(Object o) { - ... - } - - @Override - public void forEach(Consumer action) { - ... - } - - @Override - public void replaceAll(UnaryOperator operator) { - ... - } - - @Override - public void sort(Comparator c) { - ... - } - } -``` - -我们再看一下`java.util.AbstractList`的`remove()`方法,这样我们就明白为啥会抛出`UnsupportedOperationException`。 - -```java -public E remove(int index) { - throw new UnsupportedOperationException(); -} -``` - -### 2.1.4. 如何正确的将数组转换为ArrayList? - -stackoverflow:https://dwz.cn/vcBkTiTW - -**1. 自己动手实现(教育目的)** - -```java -//JDK1.5+ -static List arrayToList(final T[] array) { - final List l = new ArrayList(array.length); - - for (final T s : array) { - l.add(s); - } - return l; -} -``` - -```java -Integer [] myArray = { 1, 2, 3 }; -System.out.println(arrayToList(myArray).getClass());//class java.util.ArrayList -``` - -**2. 最简便的方法(推荐)** - -```java -List list = new ArrayList<>(Arrays.asList("a", "b", "c")) -``` - -**3. 使用 Java8 的Stream(推荐)** - -```java -Integer [] myArray = { 1, 2, 3 }; -List myList = Arrays.stream(myArray).collect(Collectors.toList()); -//基本类型也可以实现转换(依赖boxed的装箱操作) -int [] myArray2 = { 1, 2, 3 }; -List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList()); -``` - -**4. 使用 Guava(推荐)** - -对于不可变集合,你可以使用[`ImmutableList`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java)类及其[`of()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java#L101)与[`copyOf()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java#L225)工厂方法:(参数不能为空) - -```java -List il = ImmutableList.of("string", "elements"); // from varargs -List il = ImmutableList.copyOf(aStringArray); // from array -``` -对于可变集合,你可以使用[`Lists`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/Lists.java)类及其[`newArrayList()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/Lists.java#L87)工厂方法: - -```java -List l1 = Lists.newArrayList(anotherListOrCollection); // from collection -List l2 = Lists.newArrayList(aStringArray); // from array -List l3 = Lists.newArrayList("or", "string", "elements"); // from varargs -``` - -**5. 使用 Apache Commons Collections** - -```java -List list = new ArrayList(); -CollectionUtils.addAll(list, str); -``` - -**6. 使用 Java9 的 `List.of()`方法** -``` java -Integer[] array = {1, 2, 3}; -List list = List.of(array); -System.out.println(list); /* [1, 2, 3] */ -/* 不支持基本数据类型 */ -``` - -## 2.2. Collection.toArray()方法使用的坑&如何反转数组 - -该方法是一个泛型方法:` T[] toArray(T[] a);` 如果`toArray`方法中没有传递任何参数的话返回的是`Object`类型数组。 - -```java -String [] s= new String[]{ - "dog", "lazy", "a", "over", "jumps", "fox", "brown", "quick", "A" -}; -List list = Arrays.asList(s); -Collections.reverse(list); -s=list.toArray(new String[0]);//没有指定类型的话会报错 -``` - -由于JVM优化,`new String[0]`作为`Collection.toArray()`方法的参数现在使用更好,`new String[0]`就是起一个模板的作用,指定了返回数组的类型,0是为了节省空间,因为它只是为了说明返回的类型。详见: - -## 2.3. 不要在 foreach 循环里进行元素的 remove/add 操作 - -如果要进行`remove`操作,可以调用迭代器的 `remove `方法而不是集合类的 remove 方法。因为如果列表在任何时间从结构上修改创建迭代器之后,以任何方式除非通过迭代器自身`remove/add`方法,迭代器都将抛出一个`ConcurrentModificationException`,这就是单线程状态下产生的 **fail-fast 机制**。 - -> **fail-fast 机制** :多个线程对 fail-fast 集合进行修改的时候,可能会抛出ConcurrentModificationException,单线程下也会出现这种情况,上面已经提到过。 - -Java8开始,可以使用`Collection#removeIf()`方法删除满足特定条件的元素,如 -``` java -List list = new ArrayList<>(); -for (int i = 1; i <= 10; ++i) { - list.add(i); -} -list.removeIf(filter -> filter % 2 == 0); /* 删除list中的所有偶数 */ -System.out.println(list); /* [1, 3, 5, 7, 9] */ -``` - -`java.util`包下面的所有的集合类都是fail-fast的,而`java.util.concurrent`包下面的所有的类都是fail-safe的。 - -![不要在 foreach 循环里进行元素的 remove/add 操作](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019/7/foreach-remove:add.png) - - - From a057eda4a9eb3eb85e192527bb6a150818b08372 Mon Sep 17 00:00:00 2001 From: guide Date: Thu, 26 Aug 2021 16:45:07 +0800 Subject: [PATCH 18/19] =?UTF-8?q?[feat]Java=E9=9B=86=E5=90=88=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E6=B3=A8=E6=84=8F=E4=BA=8B=E9=A1=B9=E6=80=BB=E7=BB=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +- ...13\351\241\271\346\200\273\347\273\223.md" | 432 ++++++++++++++++++ 2 files changed, 434 insertions(+), 1 deletion(-) create mode 100644 "docs/java/collection/Java\351\233\206\345\220\210\344\275\277\347\224\250\346\263\250\346\204\217\344\272\213\351\241\271\346\200\273\347\273\223.md" diff --git a/README.md b/README.md index 2235b6e7874..5706e218c8d 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ **知识点/面试题** : (必看:+1: ) 1. **[Java 基础知识](docs/java/basis/Java基础知识.md)** -2. **[Java 基础知识疑难点/易错点](docs/java/basis/Java基础知识疑难点.md)** +2. [Java 基础知识疑难点/易错点](docs/java/basis/Java基础知识疑难点.md) **重要知识点详解:** @@ -61,6 +61,7 @@ 1. **[Java 容器常见问题总结](docs/java/collection/Java集合框架常见面试题.md)** (必看 :+1:) 2. **源码分析** :[ArrayList 源码+扩容机制分析](docs/java/collection/ArrayList源码+扩容机制分析.md) 、[LinkedList 源码](docs/java/collection/LinkedList源码分析.md) 、[HashMap(JDK1.8)源码+底层数据结构分析]() 、[ConcurrentHashMap 源码+底层数据结构分析](docs/java/collection/ConcurrentHashMap源码+底层数据结构分析.md) +3. [Java 容器使用注意事项总结](docs/java/collection/Java集合使用注意事项总结.md) ### 并发 diff --git "a/docs/java/collection/Java\351\233\206\345\220\210\344\275\277\347\224\250\346\263\250\346\204\217\344\272\213\351\241\271\346\200\273\347\273\223.md" "b/docs/java/collection/Java\351\233\206\345\220\210\344\275\277\347\224\250\346\263\250\346\204\217\344\272\213\351\241\271\346\200\273\347\273\223.md" new file mode 100644 index 00000000000..3c04c7afdca --- /dev/null +++ "b/docs/java/collection/Java\351\233\206\345\220\210\344\275\277\347\224\250\346\263\250\346\204\217\344\272\213\351\241\271\346\200\273\347\273\223.md" @@ -0,0 +1,432 @@ +这篇文章我根据《阿里巴巴 Java 开发手册》总结了关于集合使用常见的注意事项以及其具体原理。 + +强烈建议小伙伴们多多阅读几遍,避免自己写代码的时候出现这些低级的问题。 + +## 集合判空 + +《阿里巴巴 Java 开发手册》的描述如下: + +> **判断所有集合内部的元素是否为空,使用 `isEmpty()` 方法,而不是 `size()==0` 的方式。** + +这是因为 `isEmpty()` 方法的可读性更好,并且时间复杂度为 O(1)。 + +绝大部分我们使用的集合的 `size()` 方法的时间复杂度也是 O(1),不过,也有很多复杂度不是 O(1) 的,比如 `java.util.concurrent` 包下的某些集合(`ConcurrentLinkedQueue` 、`ConcurrentHashMap`...)。 + +下面是 `ConcurrentHashMap` 的 `size()` 方法和 `isEmpty()` 方法的源码。 + +```java +public int size() { + long n = sumCount(); + return ((n < 0L) ? 0 : + (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE : + (int)n); +} +final long sumCount() { + CounterCell[] as = counterCells; CounterCell a; + long sum = baseCount; + if (as != null) { + for (int i = 0; i < as.length; ++i) { + if ((a = as[i]) != null) + sum += a.value; + } + } + return sum; +} +public boolean isEmpty() { + return sumCount() <= 0L; // ignore transient negative values +} +``` + +## 集合转 Map + +《阿里巴巴 Java 开发手册》的描述如下: + +> **在使用 `java.util.stream.Collectors` 类的 `toMap()` 方法转为 `Map` 集合时,一定要注意当 value 为 null 时会抛 NPE 异常。** + +```java +class Person { + private String name; + private String phoneNumber; + // getters and setters +} + +List bookList = new ArrayList<>(); +bookList.add(new Person("jack","18163138123")); +bookList.add(new Person("martin",null)); +// 空指针异常 +bookList.stream().collect(Collectors.toMap(Person::getName, Person::getPhoneNumber)); +``` + +下面我们来解释一下原因。 + +首先,我们来看 `java.util.stream.Collectors` 类的 `toMap()` 方法 ,可以看到其内部调用了 `Map` 接口的 `merge()` 方法。 + +```java +public static > +Collector toMap(Function keyMapper, + Function valueMapper, + BinaryOperator mergeFunction, + Supplier mapSupplier) { + BiConsumer accumulator + = (map, element) -> map.merge(keyMapper.apply(element), + valueMapper.apply(element), mergeFunction); + return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID); +} +``` + +`Map` 接口的 `merge()` 方法如下,这个方法是接口中的默认实现。 + +> 如果你还不了解 Java 8 新特性的话,请看这篇文章:[《Java8 新特性总结》](https://mp.weixin.qq.com/s/ojyl7B6PiHaTWADqmUq2rw) 。 + +```java +default V merge(K key, V value, + BiFunction remappingFunction) { + Objects.requireNonNull(remappingFunction); + Objects.requireNonNull(value); + V oldValue = get(key); + V newValue = (oldValue == null) ? value : + remappingFunction.apply(oldValue, value); + if(newValue == null) { + remove(key); + } else { + put(key, newValue); + } + return newValue; +} +``` + +`merge()` 方法会先调用 `Objects.requireNonNull()` 方法判断 value 是否为空。 + +```java +public static T requireNonNull(T obj) { + if (obj == null) + throw new NullPointerException(); + return obj; +} +``` + +## 集合遍历 + +《阿里巴巴 Java 开发手册》的描述如下: + +> **不要在 foreach 循环里进行元素的 `remove/add` 操作。remove 元素请使用 `Iterator` 方式,如果并发操作,需要对 `Iterator` 对象加锁。** + +通过反编译你会发现 foreach 语法糖底层其实还是依赖 `Iterator` 。不过, `remove/add` 操作直接调用的是集合自己的方法,而不是 `Iterator` 的 `remove/add`方法 + +这就导致 `Iterator` 莫名其妙地发现自己有元素被 `remove/add` ,然后,它就会抛出一个 `ConcurrentModificationException` 来提示用户发生了并发修改异常。这就是单线程状态下产生的 **fail-fast 机制**。 + +> **fail-fast 机制** :多个线程对 fail-fast 集合进行修改的时候,可能会抛出`ConcurrentModificationException`。 即使是单线程下也有可能会出现这种情况,上面已经提到过。 + +Java8 开始,可以使用 `Collection#removeIf()`方法删除满足特定条件的元素,如 + +```java +List list = new ArrayList<>(); +for (int i = 1; i <= 10; ++i) { + list.add(i); +} +list.removeIf(filter -> filter % 2 == 0); /* 删除list中的所有偶数 */ +System.out.println(list); /* [1, 3, 5, 7, 9] */ +``` + +除了上面介绍的直接使用 `Iterator` 进行遍历操作之外,你还可以: + +- 使用普通的 for 循环 +- 使用 fail-safe 的集合类。`java.util`包下面的所有的集合类都是 fail-fast 的,而`java.util.concurrent`包下面的所有的类都是 fail-safe 的。 +- ...... + +## 集合去重 + +《阿里巴巴 Java 开发手册》的描述如下: + +> **可以利用 `Set` 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 `List` 的 `contains()` 进行遍历去重或者判断包含操作。** + +这里我们以 `HashSet` 和 `ArrayList` 为例说明。 + +```java +// Set 去重代码示例 +public static Set removeDuplicateBySet(List data) { + + if (CollectionUtils.isEmpty(data)) { + return new HashSet<>(); + } + return new HashSet<>(data); +} + +// List 去重代码示例 +public static List removeDuplicateByList(List data) { + + if (CollectionUtils.isEmpty(data)) { + return new ArrayList<>(); + + } + List result = new ArrayList<>(data.size()); + for (T current : data) { + if (!result.contains(current)) { + result.add(current); + } + } + return result; +} + +``` + +两者的核心差别在于 `contains()` 方法的实现。 + +`HashSet` 的 `contains()` 方法底部依赖的 `HashMap` 的 `containsKey()` 方法,时间复杂度接近于 O(1)(没有出现哈希冲突的时候为 O(1))。 + +```java +private transient HashMap map; +public boolean contains(Object o) { + return map.containsKey(o); +} +``` + +我们有 N 个元素插入进 Set 中,那时间复杂度就接近是 O (n)。 + +`ArrayList` 的 `contains()` 方法是通过遍历所有元素的方法来做的,时间复杂度接近是 O(n)。 + +```java +public boolean contains(Object o) { + return indexOf(o) >= 0; +} +public int indexOf(Object o) { + if (o == null) { + for (int i = 0; i < size; i++) + if (elementData[i]==null) + return i; + } else { + for (int i = 0; i < size; i++) + if (o.equals(elementData[i])) + return i; + } + return -1; +} + +``` + +我们的 `List` 有 N 个元素,那时间复杂度就接近是 O (n^2)。 + +## 集合转数组 + +《阿里巴巴 Java 开发手册》的描述如下: + +> **使用集合转数组的方法,必须使用集合的 `toArray(T[] array)`,传入的是类型完全一致、长度为 0 的空数组。** + +`toArray(T[] array)` 方法的参数是一个泛型数组,如果 `toArray` 方法中没有传递任何参数的话返回的是 `Object`类 型数组。 + +```java +String [] s= new String[]{ + "dog", "lazy", "a", "over", "jumps", "fox", "brown", "quick", "A" +}; +List list = Arrays.asList(s); +Collections.reverse(list); +//没有指定类型的话会报错 +s=list.toArray(new String[0]); +``` + +由于 JVM 优化,`new String[0]`作为`Collection.toArray()`方法的参数现在使用更好,`new String[0]`就是起一个模板的作用,指定了返回数组的类型,0 是为了节省空间,因为它只是为了说明返回的类型。详见: + +## 数组转集合 + +《阿里巴巴 Java 开发手册》的描述如下: + +> **使用工具类 `Arrays.asList()` 把数组转换成集合时,不能使用其修改集合相关的方法, 它的 `add/remove/clear` 方法会抛出 `UnsupportedOperationException` 异常。** + +我在之前的一个项目中就遇到一个类似的坑。 + +`Arrays.asList()`在平时开发中还是比较常见的,我们可以使用它将一个数组转换为一个 `List` 集合。 + +```java +String[] myArray = {"Apple", "Banana", "Orange"}; +List myList = Arrays.asList(myArray); +//上面两个语句等价于下面一条语句 +List myList = Arrays.asList("Apple","Banana", "Orange"); +``` + +JDK 源码对于这个方法的说明: + +```java +/** + *返回由指定数组支持的固定大小的列表。此方法作为基于数组和基于集合的API之间的桥梁, + * 与 Collection.toArray()结合使用。返回的List是可序列化并实现RandomAccess接口。 + */ +public static List asList(T... a) { + return new ArrayList<>(a); +} +``` + +下面我们来总结一下使用注意事项。 + +**1、`Arrays.asList()`是泛型方法,传递的数组必须是对象数组,而不是基本类型。** + +```java +int[] myArray = {1, 2, 3}; +List myList = Arrays.asList(myArray); +System.out.println(myList.size());//1 +System.out.println(myList.get(0));//数组地址值 +System.out.println(myList.get(1));//报错:ArrayIndexOutOfBoundsException +int[] array = (int[]) myList.get(0); +System.out.println(array[0]);//1 +``` + +当传入一个原生数据类型数组时,`Arrays.asList()` 的真正得到的参数就不是数组中的元素,而是数组对象本身!此时 `List` 的唯一元素就是这个数组,这也就解释了上面的代码。 + +我们使用包装类型数组就可以解决这个问题。 + +```java +Integer[] myArray = {1, 2, 3}; +``` + +**2、使用集合的修改方法: `add()`、`remove()`、`clear()`会抛出异常。** + +```java +List myList = Arrays.asList(1, 2, 3); +myList.add(4);//运行时报错:UnsupportedOperationException +myList.remove(1);//运行时报错:UnsupportedOperationException +myList.clear();//运行时报错:UnsupportedOperationException +``` + +`Arrays.asList()` 方法返回的并不是 `java.util.ArrayList` ,而是 `java.util.Arrays` 的一个内部类,这个内部类并没有实现集合的修改方法或者说并没有重写这些方法。 + +```java +List myList = Arrays.asList(1, 2, 3); +System.out.println(myList.getClass());//class java.util.Arrays$ArrayList +``` + +下图是 `java.util.Arrays$ArrayList` 的简易源码,我们可以看到这个类重写的方法有哪些。 + +```java + private static class ArrayList extends AbstractList + implements RandomAccess, java.io.Serializable + { + ... + + @Override + public E get(int index) { + ... + } + + @Override + public E set(int index, E element) { + ... + } + + @Override + public int indexOf(Object o) { + ... + } + + @Override + public boolean contains(Object o) { + ... + } + + @Override + public void forEach(Consumer action) { + ... + } + + @Override + public void replaceAll(UnaryOperator operator) { + ... + } + + @Override + public void sort(Comparator c) { + ... + } + } +``` + +我们再看一下`java.util.AbstractList`的 `add/remove/clear` 方法就知道为什么会抛出 `UnsupportedOperationException` 了。 + +```java +public E remove(int index) { + throw new UnsupportedOperationException(); +} +public boolean add(E e) { + add(size(), e); + return true; +} +public void add(int index, E element) { + throw new UnsupportedOperationException(); +} + +public void clear() { + removeRange(0, size()); +} +protected void removeRange(int fromIndex, int toIndex) { + ListIterator it = listIterator(fromIndex); + for (int i=0, n=toIndex-fromIndex; i List arrayToList(final T[] array) { + final List l = new ArrayList(array.length); + + for (final T s : array) { + l.add(s); + } + return l; +} + + +Integer [] myArray = { 1, 2, 3 }; +System.out.println(arrayToList(myArray).getClass());//class java.util.ArrayList +``` + +2、最简便的方法 + +```java +List list = new ArrayList<>(Arrays.asList("a", "b", "c")) +``` + +3、使用 Java8 的 `Stream`(推荐) + +```java +Integer [] myArray = { 1, 2, 3 }; +List myList = Arrays.stream(myArray).collect(Collectors.toList()); +//基本类型也可以实现转换(依赖boxed的装箱操作) +int [] myArray2 = { 1, 2, 3 }; +List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList()); +``` + +4、使用 Guava + +对于不可变集合,你可以使用[`ImmutableList`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java)类及其[`of()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java#L101)与[`copyOf()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java#L225)工厂方法:(参数不能为空) + +```java +List il = ImmutableList.of("string", "elements"); // from varargs +List il = ImmutableList.copyOf(aStringArray); // from array +``` + +对于可变集合,你可以使用[`Lists`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/Lists.java)类及其[`newArrayList()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/Lists.java#L87)工厂方法: + +```java +List l1 = Lists.newArrayList(anotherListOrCollection); // from collection +List l2 = Lists.newArrayList(aStringArray); // from array +List l3 = Lists.newArrayList("or", "string", "elements"); // from varargs +``` + +5、使用 Apache Commons Collections + +```java +List list = new ArrayList(); +CollectionUtils.addAll(list, str); +``` + +6、 使用 Java9 的 `List.of()`方法 + +```java +Integer[] array = {1, 2, 3}; +List list = List.of(array); +``` \ No newline at end of file From c2f10ad2d5f2577797bc7ed3f11ffe7267081dd2 Mon Sep 17 00:00:00 2001 From: guide Date: Thu, 26 Aug 2021 23:05:40 +0800 Subject: [PATCH 19/19] =?UTF-8?q?Delete=20ThreadLocal=EF=BC=88=E6=9C=AA?= =?UTF-8?q?=E5=AE=8C=E6=88=90=EF=BC=89.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...52\345\256\214\346\210\220\357\274\211.md" | 170 ------------------ 1 file changed, 170 deletions(-) delete mode 100644 "docs/java/multi-thread/ThreadLocal\357\274\210\346\234\252\345\256\214\346\210\220\357\274\211.md" diff --git "a/docs/java/multi-thread/ThreadLocal\357\274\210\346\234\252\345\256\214\346\210\220\357\274\211.md" "b/docs/java/multi-thread/ThreadLocal\357\274\210\346\234\252\345\256\214\346\210\220\357\274\211.md" deleted file mode 100644 index f69cc1d60e9..00000000000 --- "a/docs/java/multi-thread/ThreadLocal\357\274\210\346\234\252\345\256\214\346\210\220\357\274\211.md" +++ /dev/null @@ -1,170 +0,0 @@ -[ThreadLocal造成OOM内存溢出案例演示与原理分析](https://blog.csdn.net/xlgen157387/article/details/78298840) - -[深入理解 Java 之 ThreadLocal 工作原理]() - -## ThreadLocal - -### ThreadLocal简介 - -通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。**如果想实现每一个线程都有自己的专属本地变量该如何解决呢?** JDK中提供的`ThreadLocal`类正是为了解决这样的问题。 **`ThreadLocal`类主要解决的就是让每个线程绑定自己的值,可以将`ThreadLocal`类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。** - -**如果你创建了一个`ThreadLocal`变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是`ThreadLocal`变量名的由来。他们可以使用 `get()` 和 `set()` 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。** - -再举个简单的例子: - -比如有两个人去宝屋收集宝物,这两个共用一个袋子的话肯定会产生争执,但是给他们两个人每个人分配一个袋子的话就不会出现这样的问题。如果把这两个人比作线程的话,那么ThreadLocal就是用来这两个线程竞争的。 - -### ThreadLocal示例 - -相信看了上面的解释,大家已经搞懂 ThreadLocal 类是个什么东西了。 - -```java -import java.text.SimpleDateFormat; -import java.util.Random; - -public class ThreadLocalExample implements Runnable{ - - // SimpleDateFormat 不是线程安全的,所以每个线程都要有自己独立的副本 - private static final ThreadLocal formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm")); - - public static void main(String[] args) throws InterruptedException { - ThreadLocalExample obj = new ThreadLocalExample(); - for(int i=0 ; i<10; i++){ - Thread t = new Thread(obj, ""+i); - Thread.sleep(new Random().nextInt(1000)); - t.start(); - } - } - - @Override - public void run() { - System.out.println("Thread Name= "+Thread.currentThread().getName()+" default Formatter = "+formatter.get().toPattern()); - try { - Thread.sleep(new Random().nextInt(1000)); - } catch (InterruptedException e) { - e.printStackTrace(); - } - //formatter pattern is changed here by thread, but it won't reflect to other threads - formatter.set(new SimpleDateFormat()); - - System.out.println("Thread Name= "+Thread.currentThread().getName()+" formatter = "+formatter.get().toPattern()); - } - -} - -``` - -Output: - -``` -Thread Name= 0 default Formatter = yyyyMMdd HHmm -Thread Name= 0 formatter = yy-M-d ah:mm -Thread Name= 1 default Formatter = yyyyMMdd HHmm -Thread Name= 2 default Formatter = yyyyMMdd HHmm -Thread Name= 1 formatter = yy-M-d ah:mm -Thread Name= 3 default Formatter = yyyyMMdd HHmm -Thread Name= 2 formatter = yy-M-d ah:mm -Thread Name= 4 default Formatter = yyyyMMdd HHmm -Thread Name= 3 formatter = yy-M-d ah:mm -Thread Name= 4 formatter = yy-M-d ah:mm -Thread Name= 5 default Formatter = yyyyMMdd HHmm -Thread Name= 5 formatter = yy-M-d ah:mm -Thread Name= 6 default Formatter = yyyyMMdd HHmm -Thread Name= 6 formatter = yy-M-d ah:mm -Thread Name= 7 default Formatter = yyyyMMdd HHmm -Thread Name= 7 formatter = yy-M-d ah:mm -Thread Name= 8 default Formatter = yyyyMMdd HHmm -Thread Name= 9 default Formatter = yyyyMMdd HHmm -Thread Name= 8 formatter = yy-M-d ah:mm -Thread Name= 9 formatter = yy-M-d ah:mm -``` - -从输出中可以看出,Thread-0已经改变了formatter的值,但仍然是thread-2默认格式化程序与初始化值相同,其他线程也一样。 - -上面有一段代码用到了创建 `ThreadLocal` 变量的那段代码用到了 Java8 的知识,它等于下面这段代码,如果你写了下面这段代码的话,IDEA会提示你转换为Java8的格式(IDEA真的不错!)。因为ThreadLocal类在Java 8中扩展,使用一个新的方法`withInitial()`,将Supplier功能接口作为参数。 - -```java - private static final ThreadLocal formatter = new ThreadLocal(){ - @Override - protected SimpleDateFormat initialValue() - { - return new SimpleDateFormat("yyyyMMdd HHmm"); - } - }; -``` - -### ThreadLocal原理 - -从 `Thread`类源代码入手。 - -```java -public class Thread implements Runnable { - ...... -//与此线程有关的ThreadLocal值。由ThreadLocal类维护 -ThreadLocal.ThreadLocalMap threadLocals = null; - -//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护 -ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; - ...... -} -``` - -从上面`Thread`类 源代码可以看出`Thread` 类中有一个 `threadLocals` 和 一个 `inheritableThreadLocals` 变量,它们都是 `ThreadLocalMap` 类型的变量,我们可以把 `ThreadLocalMap` 理解为`ThreadLocal` 类实现的定制化的 `HashMap`。默认情况下这两个变量都是null,只有当前线程调用 `ThreadLocal` 类的 `set`或`get`方法时才创建它们,实际上调用这两个方法的时候,我们调用的是`ThreadLocalMap`类对应的 `get()`、`set() `方法。 - -`ThreadLocal`类的`set()`方法 - -```java - public void set(T value) { - Thread t = Thread.currentThread(); - ThreadLocalMap map = getMap(t); - if (map != null) - map.set(this, value); - else - createMap(t, value); - } - ThreadLocalMap getMap(Thread t) { - return t.threadLocals; - } -``` - -通过上面这些内容,我们足以通过猜测得出结论:**最终的变量是放在了当前线程的 `ThreadLocalMap` 中,并不是存在 `ThreadLocal` 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值。** - -**每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为key的键值对。** 比如我们在同一个线程中声明了两个 `ThreadLocal` 对象的话,会使用 `Thread`内部都是使用仅有那个`ThreadLocalMap` 存放数据的,`ThreadLocalMap`的 key 就是 `ThreadLocal`对象,value 就是 `ThreadLocal` 对象调用`set`方法设置的值。`ThreadLocal` 是 map结构是为了让每个线程可以关联多个 `ThreadLocal`变量。这也就解释了ThreadLocal声明的变量为什么在每一个线程都有自己的专属本地变量。 - -```java -public class Thread implements Runnable { - ...... -//与此线程有关的ThreadLocal值。由ThreadLocal类维护 -ThreadLocal.ThreadLocalMap threadLocals = null; - -//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护 -ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; - ...... -} -``` - -`ThreadLocalMap`是`ThreadLocal`的静态内部类。 - - - -### ThreadLocal 内存泄露问题 - -`ThreadLocalMap` 中使用的 key 为 `ThreadLocal` 的弱引用,而 value 是强引用。所以,如果 `ThreadLocal` 没有被外部强引用的情况下,在垃圾回收的时候会 key 会被清理掉,而 value 不会被清理掉。这样一来,`ThreadLocalMap` 中就会出现key为null的Entry。假如我们不做任何措施的话,value 永远无法被GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调用 `set()`、`get()`、`remove()` 方法的时候,会清理掉 key 为 null 的记录。使用完 `ThreadLocal`方法后 最好手动调用`remove()`方法 - -```java - static class Entry extends WeakReference> { - /** The value associated with this ThreadLocal. */ - Object value; - - Entry(ThreadLocal k, Object v) { - super(k); - value = v; - } - } -``` - -**弱引用介绍:** - -> 如果一个对象只具有弱引用,那就类似于**可有可无的生活用品**。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。 -> -> 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。