1
0
mirror of https://github.com/Snailclimb/JavaGuide synced 2025-06-20 22:17:09 +08:00

Merge branch 'master' into master

This commit is contained in:
Vanilla 2020-12-10 21:07:40 +08:00 committed by GitHub
commit 8fe56a64af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 126 additions and 119 deletions

View File

@ -1,5 +1,4 @@
<!-- @import "[TOC]" {cmd="toc" depthFrom=1 depthTo=6 orderedList=false} -->
<!-- code_chunk_output -->
@ -19,16 +18,16 @@
- [1.2.1. 字符型常量和字符串常量的区别?](#121-字符型常量和字符串常量的区别)
- [1.2.2. 关于注释?](#122-关于注释)
- [1.2.3. 标识符和关键字的区别是什么?](#123-标识符和关键字的区别是什么)
- [1.2.4. Java中有哪些常见的关键字?](#124-java中有哪些常见的关键字)
- [1.2.4. Java 中有哪些常见的关键字?](#124-java-中有哪些常见的关键字)
- [1.2.5. 自增自减运算符](#125-自增自减运算符)
- [1.2.6. continue、break、和return的区别是什么](#126-continue-break-和return的区别是什么)
- [1.2.7. Java泛型了解么?什么是类型擦除?介绍一下常用的通配符?](#127-java泛型了解么什么是类型擦除介绍一下常用的通配符)
- [1.2.8. ==和equals的区别](#128-和equals的区别)
- [1.2.6. continue、break、和 return 的区别是什么?](#126-continue-break-和-return-的区别是什么)
- [1.2.7. Java 泛型了解么?什么是类型擦除?介绍一下常用的通配符?](#127-java-泛型了解么什么是类型擦除介绍一下常用的通配符)
- [1.2.8. ==和 equals 的区别](#128-和-equals-的区别)
- [1.2.9. hashCode()与 equals()](#129-hashcode与-equals)
- [1.3. 基本数据类型](#13-基本数据类型)
- [1.3.1. Java中的几种基本数据类型是什么?对应的包装类型是什么?各自占用多少字节呢?](#131-java中的几种基本数据类型是什么对应的包装类型是什么各自占用多少字节呢)
- [1.3.1. Java 中的几种基本数据类型是什么?对应的包装类型是什么?各自占用多少字节呢?](#131-java-中的几种基本数据类型是什么对应的包装类型是什么各自占用多少字节呢)
- [1.3.2. 自动装箱与拆箱](#132-自动装箱与拆箱)
- [1.3.3. 8种基本类型的包装类和常量池](#133-8种基本类型的包装类和常量池)
- [1.3.3. 8 种基本类型的包装类和常量池](#133-8-种基本类型的包装类和常量池)
- [1.4. 方法(函数)](#14-方法函数)
- [1.4.1. 什么是方法的返回值?返回值在类的方法里的作用是什么?](#141-什么是方法的返回值返回值在类的方法里的作用是什么)
- [1.4.2. 为什么 Java 中只有值传递?](#142-为什么-java-中只有值传递)
@ -85,7 +84,6 @@
<!-- /code_chunk_output -->
## 1. Java 基本功
### 1.1. Java 入门(基础概念与常识)
@ -183,10 +181,11 @@ Java 语言既具有编译型语言的特征,也具有解释型语言的特征
#### 1.2.1. 字符型常量和字符串常量的区别?
1. 形式上: 字符常量是单引号引起的一个字符; 字符串常量是双引号引起的0个或若干个字符
1. 形式上: 字符常量是单引号引起的一个字符; 字符串常量是双引号引起的 0 个或若干个字符
2. 含义上: 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)
3. 占内存大小 字符常量只占 2 个字节; 字符串常量占若干个字节 (**注意: char 在 Java 中占两个字节**),
> 字符封装类 `Character` 有一个成员常量 `Character.SIZE` 值为16,单位是`bits`,该值除以8(`1byte=8bits`)后就可以得到2个字节
> 字符封装类 `Character` 有一个成员常量 `Character.SIZE` 值为 16,单位是`bits`,该值除以 8(`1byte=8bits`)后就可以得到 2 个字节
> java 编程思想第四版2.2.2 节
> ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-15/86735519.jpg)
@ -228,7 +227,7 @@ Java 中的注释有三种:
在我们编写程序的时候需要大量地为程序、类、变量、方法等取名字于是就有了标识符简单来说标识符就是一个名字。但是有一些标识符Java 语言已经赋予了其特殊的含义,只能用于特定的地方,这种特殊的标识符就是关键字。因此,关键字是被赋予特殊含义的标识符。比如,在我们的日常生活中 ,“警察局”这个名字已经被赋予了特殊的含义,所以如果你开一家店,店的名字不能叫“警察局”,“警察局”就是我们日常生活中的关键字。
#### 1.2.4. Java中有哪些常见的关键字
#### 1.2.4. Java 中有哪些常见的关键字?
| 访问控制 | private | protected | public | | | | |
| -------------------- | -------- | ---------- | -------- | ------------ | ---------- | --------- | ------ |
@ -249,7 +248,7 @@ Java 中的注释有三种:
++和--运算符可以放在变量之前,也可以放在变量之后,当运算符放在变量之前时(前缀),先自增/减,再赋值;当运算符放在变量之后时(后缀),先赋值,再自增/减。例如,当 `b = ++a` 时,先自增(自己增加 1再赋值赋值给 b`b = a++` 时,先赋值(赋值给 b),再自增(自己增加 1。也就是++a 输出的是 a+1 的值a++输出的是 a 值。用一句口诀就是:“符号在前就先加/减,符号在后就后加/减”。
#### 1.2.6. continue、break、和return的区别是什么
#### 1.2.6. continue、break、和 return 的区别是什么?
在循环结构中,当循环条件不满足或者循环次数达到要求时,循环会正常结束。但是,有时候可能需要在循环的过程中,当发生了某种条件之后 ,提前终止循环,这就需要用到下面几个关键词:
@ -261,11 +260,11 @@ return 用于跳出所在方法结束该方法的运行。return 一般有两
1. `return;` :直接使用 return 结束方法执行,用于没有返回值函数的方法
2. `return value;` return 一个特定值,用于有返回值函数的方法
#### 1.2.7. Java泛型了解么什么是类型擦除介绍一下常用的通配符
#### 1.2.7. Java 泛型了解么?什么是类型擦除?介绍一下常用的通配符?
Java 泛型generics是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
**Java的泛型是伪泛型这是因为Java在编译期间所有的泛型信息都会被擦掉这也就是通常所说类型擦除 。** 更多关于类型擦除的问题,可以查看这篇文章:[《Java泛型类型擦除以及类型擦除带来的问题》](https://www.cnblogs.com/wuqinglong/p/9456193.html) 。
**Java 的泛型是伪泛型,这是因为 Java 在编译期间,所有的泛型信息都会被擦掉,这也就是通常所说类型擦除 。** 更多关于类型擦除的问题,可以查看这篇文章:[《Java 泛型类型擦除以及类型擦除带来的问题》](https://www.cnblogs.com/wuqinglong/p/9456193.html) 。
```java
List<Integer> list = new ArrayList<>();
@ -363,13 +362,13 @@ printArray( stringArray );
**常用的通配符为: TEKV**
- 表示不确定的 java 类型
- T (type) 表示具体的一个java类型
- K V (key value) 分别代表java键值中的Key Value
- E (element) 代表Element
- T (type) 表示具体的一个 java 类型
- K V (key value) 分别代表 java 键值中的 Key Value
- E (element) 代表 Element
更多关于Java 泛型中的通配符可以查看这篇文章:[《聊一聊-JAVA 泛型中的通配符 TEKV](https://juejin.im/post/5d5789d26fb9a06ad0056bd9)
更多关于 Java 泛型中的通配符可以查看这篇文章:[《聊一聊-JAVA 泛型中的通配符 TEKV](https://juejin.im/post/5d5789d26fb9a06ad0056bd9)
#### 1.2.8. ==和equals的区别
#### 1.2.8. ==和 equals 的区别
**`==`** : 它的作用是判断两个对象的地址是不是相等。即判断两个对象是不是同一个对象。(**基本数据类型==比较的是值,引用数据类型==比较的是内存地址**)
@ -387,7 +386,7 @@ public boolean equals(Object obj) {
`equals()` 方法存在两种使用情况:
- 情况 1类没有覆盖 `equals()`方法。则通过` equals()`比较该类的两个对象时,等价于通过“==”比较这两个对象。使用的默认是 `Object``equals()`方法。
- 情况 1类没有覆盖 `equals()`方法。则通过`equals()`比较该类的两个对象时,等价于通过“==”比较这两个对象。使用的默认是 `Object``equals()`方法。
- 情况 2类覆盖了 `equals()`方法。一般,我们都覆盖 `equals()`方法来两个对象的内容相等;若它们的内容相等,则返回 true(即,认为这两个对象相等)。
**举个例子:**
@ -445,11 +444,11 @@ public boolean equals(Object anObject) {
#### 1.2.9. hashCode()与 equals()
面试官可能会问你:“你重写过 `hashcode``equals `么,为什么重写 `equals` 时必须重写 `hashCode` 方法?”
面试官可能会问你:“你重写过 `hashcode``equals`么,为什么重写 `equals` 时必须重写 `hashCode` 方法?”
**1)hashCode()介绍:**
`hashCode()` 的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。`hashCode() `定义在 JDK 的 `Object` 类中,这就意味着 Java 中的任何类都包含有 `hashCode()` 函数。另外需要注意的是: `Object` 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法通常用来将对象的 内存地址 转换为整数之后返回。
`hashCode()` 的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。`hashCode()`定义在 JDK 的 `Object` 类中,这就意味着 Java 中的任何类都包含有 `hashCode()` 函数。另外需要注意的是: `Object` 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法通常用来将对象的 内存地址 转换为整数之后返回。
```java
public native int hashCode();
@ -477,20 +476,17 @@ public native int hashCode();
我们刚刚也提到了 `HashSet`,如果 `HashSet` 在对比的时候,同样的 hashcode 有多个对象,它会使用 `equals()` 来判断是否真的相同。也就是说 `hashcode` 只是用来缩小查找成本。
更多关于 `hashcode()``equals()` 的内容可以查看:[Java hashCode() 和 equals()的若干问题解答](https://www.cnblogs.com/skywang12345/p/3324958.html)
### 1.3. 基本数据类型
#### 1.3.1. Java中的几种基本数据类型是什么对应的包装类型是什么各自占用多少字节呢
#### 1.3.1. Java 中的几种基本数据类型是什么?对应的包装类型是什么?各自占用多少字节呢?
Java**中**有8种基本数据类型分别为
Java**中**有 8 种基本数据类型,分别为:
1. 6种数字类型 byte、short、int、long、float、double
2. 1种字符类型char
3. 1种布尔型boolean。
1. 6 种数字类型 byte、short、int、long、float、double
2. 1 种字符类型char
3. 1 种布尔型boolean。
这八种基本类型都有对应的包装类分别为Byte、Short、Integer、Long、Float、Double、Character、Boolean
@ -505,7 +501,7 @@ Java**中**有8种基本数据类型分别为
| double | 64 | 8 | 0d |
| boolean | 1 | | false |
对于boolean官方文档未明确定义它依赖于 JVM 厂商的具体实现。逻辑上理解是占用 1位但是实际中会考虑计算机高效存储因素。
对于 boolean官方文档未明确定义它依赖于 JVM 厂商的具体实现。逻辑上理解是占用 1 位,但是实际中会考虑计算机高效存储因素。
注意:
@ -519,9 +515,9 @@ Java**中**有8种基本数据类型分别为
更多内容见:[深入剖析 Java 中的装箱和拆箱](https://www.cnblogs.com/dolphin0520/p/3780005.html)
#### 1.3.3. 8种基本类型的包装类和常量池
#### 1.3.3. 8 种基本类型的包装类和常量池
**Java 基本类型的包装类的大部分都实现了常量池技术,即 Byte,Short,Integer,Long,Character,Boolean前面 4 种包装类默认创建了数值[-128127] 的相应类型的缓存数据Character创建了数值在[0,127]范围的缓存数据Boolean 直接返回True Or False。如果超出对应范围仍然会去创建新的对象。** 为啥把缓存设置为[-128127]区间?([参见issue/461](https://github.com/Snailclimb/JavaGuide/issues/461))性能和资源之间的权衡。
**Java 基本类型的包装类的大部分都实现了常量池技术,即 Byte,Short,Integer,Long,Character,Boolean前面 4 种包装类默认创建了数值[-128127] 的相应类型的缓存数据Character 创建了数值在[0,127]范围的缓存数据Boolean 直接返回 True Or False。如果超出对应范围仍然会去创建新的对象。** 为啥把缓存设置为[-128127]区间?([参见 issue/461](https://github.com/Snailclimb/JavaGuide/issues/461))性能和资源之间的权衡。
```java
public static Boolean valueOf(boolean b) {
@ -570,6 +566,7 @@ private static class CharacterCache {
```
**应用场景:**
1. Integer i1=40Java 在编译的时候会直接将代码封装成 Integer i1=Integer.valueOf(40);,从而使用常量池中的对象。
2. Integer i1 = new Integer(40);这种情况下会创建新的对象。
@ -578,6 +575,7 @@ private static class CharacterCache {
Integer i2 = new Integer(40);
System.out.println(i1 == i2);//输出 false
```
**Integer 比较更丰富的一个例子:**
```java
@ -787,7 +785,7 @@ Java 程序设计语言对对象采用的不是引用调用,实际上,对象
暖心的 Guide 哥最后再来个图表总结一下!
| 区别点 | 重载方法 | 重写方法 |
| :--------- | :------- | :----------------------------------------------------------- |
| :--------- | :------- | :--------------------------------------------------------------- |
| 发生范围 | 同一个类 | 子类 |
| 参数列表 | 必须修改 | 一定不能修改 |
| 返回类型 | 可修改 | 子类方法返回值类型应比父类方法返回值类型更小或相等 |
@ -795,7 +793,6 @@ Java 程序设计语言对对象采用的不是引用调用,实际上,对象
| 访问修饰符 | 可修改 | 一定不能做更严格的限制(可以降低限制) |
| 发生阶段 | 编译期 | 运行期 |
**方法的重写要遵循“两同两小一大”**(以下内容摘录自《疯狂 Java 讲义》,[issue#892](https://github.com/Snailclimb/JavaGuide/issues/892)
- “两同”即方法名相同、形参列表相同;
@ -985,7 +982,7 @@ public class Student {
#### 2.5.1. String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?
简单的来说:`String` 类中使用 final 关键字修饰字符数组来保存字符串,`private final char value[]`,所以` String` 对象是不可变的。
简单的来说:`String` 类中使用 final 关键字修饰字符数组来保存字符串,`private final char value[]`,所以`String` 对象是不可变的。
> 补充(来自[issue 675](https://github.com/Snailclimb/JavaGuide/issues/675)):在 Java 9 之后String 类的实现改用 byte 数组存储字符串 `private final byte[] value`;
@ -1091,7 +1088,6 @@ public class test1 {
- String 中的 equals 方法是被重写过的,因为 object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。
- 当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。
#### 2.5.4. hashCode 与 equals (重要)
面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写 equals 时必须重写 hashCode 方法?”
@ -1174,25 +1170,34 @@ JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道
#### 3.2.1. Java 异常类层次结构图
![](images/Java异常类层次结构图.png)
![](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/2020-12/Java%E5%BC%82%E5%B8%B8%E7%B1%BB%E5%B1%82%E6%AC%A1%E7%BB%93%E6%9E%84%E5%9B%BE.png)
<p style="font-size:13px;text-align:right">图片来自https://simplesnippets.tech/exception-handling-in-java-part-1/</p>
![](images/Java异常类层次结构图2.png)
![](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/2020-12/Java%E5%BC%82%E5%B8%B8%E7%B1%BB%E5%B1%82%E6%AC%A1%E7%BB%93%E6%9E%84%E5%9B%BE2.png)
<p style="font-size:13px;text-align:right">图片来自https://chercher.tech/java-programming/exceptions-java</p>
在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 **Throwable 类**。Throwable 有两个重要的子类:**Exception异常** 和 **Error错误** ,二者都是 Java 异常处理的重要子类,各自都包含大量子类
在 Java 中,所有的异常都有一个共同的祖先 `java.lang` 包中的 `Throwable` 类。`Throwable` 类有两个重要的子类 `Exception`(异常)和 `Error`(错误)。`Exception` 能被程序本身处理(`try-catch`) `Error` 是无法处理的(只能尽量避免)
**Error错误:是程序无法处理的错误**,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVMJava 虚拟机出现的问题。例如Java 虚拟机运行错误Virtual MachineError当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时Java 虚拟机JVM一般会选择线程终止
`Exception``Error` 二者都是 Java 异常处理的重要子类,各自都包含大量子类
这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如 Java 虚拟机运行错误Virtual MachineError、类定义错误NoClassDefFoundError等。这些错误是不可查的因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java 中,错误通过 Error 的子类描述。
- **`Exception`** :程序本身可以处理的异常,可以通过 `catch` 来进行捕获。`Exception` 又可以分为 受检查异常(必须处理) 和 不受检查异常(可以不处理)。
- **`Error`** `Error` 属于程序无法处理的错误 ,我们没办法通过 `catch` 来进行捕获 。例如Java 虚拟机运行错误(`Virtual MachineError`)、虚拟机内存不够错误(`OutOfMemoryError`)、类定义错误(`NoClassDefFoundError`)等 。这些异常发生时Java 虚拟机JVM一般会选择线程终止。
**Exception异常:是程序本身可以处理的异常**。</font>Exception 类有一个重要的子类 **RuntimeException**。RuntimeException 异常由 Java 虚拟机抛出。**NullPointerException**(要访问的变量没有引用任何对象时,抛出该异常)、**ArithmeticException**(算术运算异常,一个整数除以 0 时,抛出该异常)和 **ArrayIndexOutOfBoundsException** (下标越界异常)。
**受检查异常**
**注意:异常和错误的区别:异常能被程序本身处理,错误是无法处理。**
Java 代码在编译过程中,如果受检查异常没有被 `catch`/`throw` 处理的话,就没办法通过编译 。比如下面这段 IO 操作的代码。
![check-exception](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/2020-12/check-exception.png)
除了`RuntimeException`及其子类以外,其他的`Exception`类及其子类都属于检查异常 。常见的受检查异常有: IO 相关的异常、`ClassNotFoundException``SQLException`...。
**不受检查异常**
Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。
`RuntimeException` 及其子类都统称为非受检查异常,例如:`NullPointExecrption``NumberFormatException`(字符串转换为数字)、`ArrayIndexOutOfBoundsException`(数组越界)、`ClassCastException`(类型转换错误)、`ArithmeticException`(算术错误)等。
#### 3.2.2. Throwable 类常用方法
@ -1203,14 +1208,14 @@ JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道
#### 3.2.3. try-catch-finally
- **try 块:** 用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
- **catch 块:** 用于处理 try 捕获到的异常。
- **finally 块:** 无论是否捕获或处理异常finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时finally 语句块将在方法返回之前被执行。
- **`try`块:** 用于捕获异常。其后可接零个或多个 `catch` 块,如果没有 `catch` 块,则必须跟一个 `finally` 块。
- **`catch`块:** 用于处理 try 捕获到的异常。
- **`finally` 块:** 无论是否捕获或处理异常,`finally` 块里的语句都会被执行。当在 `try` 块或 `catch` 块中遇到 `return` 语句时,`finally` 语句块将在方法返回之前被执行。
**在以下 4 种特殊情况下finally 块不会被执行:**
**在以下 4 种特殊情况下,`finally` 块不会被执行:**
1. 在 finally 语句块第一行发生了异常。 因为在其他行finally 块还是会得到执行
2. 在前面的代码中用了 System.exit(int)已退出程序。 exit 是带参函数 若该语句在异常语句之后finally 会执行
1. 在 `finally` 语句块第一行发生了异常。 因为在其他行,`finally` 块还是会得到执行
2. 在前面的代码中用了 `System.exit(int)`已退出程序。 exit 是带参函数 若该语句在异常语句之后finally 会执行
3. 程序所在的线程死亡。
4. 关闭 CPU。
@ -1237,7 +1242,7 @@ public class Test {
#### 3.2.4. 使用 `try-with-resources` 来代替`try-catch-finally`
1. **适用范围(资源的定义):** 任何实现 `java.lang.AutoCloseable`或者``java.io.Closeable` 的对象
2. **关闭资源和final的执行顺序** 在 `try-with-resources` 语句中,任何 catch 或 finally 块在声明的资源关闭后运行
2. **关闭资源和 final 的执行顺序:** 在 `try-with-resources` 语句中,任何 catch 或 finally 块在声明的资源关闭后运行
《Effecitve Java》中明确指出
@ -1262,7 +1267,7 @@ Java 中类似于`InputStream`、`OutputStream` 、`Scanner` 、`PrintWriter`等
}
```
使用Java 7之后的 `try-with-resources` 语句改造上面的代码:
使用 Java 7 之后的 `try-with-resources` 语句改造上面的代码:
```java
try (Scanner scanner = new Scanner(new File("test.txt"))) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

View File

@ -83,9 +83,9 @@
#### 1.1.3.3. Map
- `HashMap` JDK1.8 之前 HashMap 由数组+链表组成的,数组是 HashMap 的主体链表则是主要为了解决哈希冲突而存在的“拉链法”解决冲突。JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8将链表转换成红黑树前会判断如果当前数组的长度小于 64那么会选择先进行数组扩容而不是转换为红黑树将链表转化为红黑树以减少搜索时间
- `HashMap` JDK1.8 之前 `HashMap` 由数组+链表组成的,数组是 `HashMap` 的主体链表则是主要为了解决哈希冲突而存在的“拉链法”解决冲突。JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8将链表转换成红黑树前会判断如果当前数组的长度小于 64那么会选择先进行数组扩容而不是转换为红黑树将链表转化为红黑树以减少搜索时间
- `LinkedHashMap` `LinkedHashMap` 继承自 `HashMap`,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,`LinkedHashMap` 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:[《LinkedHashMap 源码详细分析JDK1.8)》](https://www.imooc.com/article/22931)
- `Hashtable` 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
- `Hashtable` 数组+链表组成的,数组是 `HashMap` 的主体,链表则是主要为了解决哈希冲突而存在的
- `TreeMap` 红黑树(自平衡的排序二叉树)
### 1.1.4. 如何选用集合?
@ -106,8 +106,8 @@
### 1.2.1. Arraylist 和 Vector 的区别?
1. ArrayList 是 List 的主要实现类,底层使用 Object[ ]存储,适用于频繁的查找工作,线程不安全
2. Vector 是 List 的古老实现类,底层使用 Object[ ]存储,线程安全的。
- `ArrayList``List` 的主要实现类,底层使用 `Object[ ]`存储,适用于频繁的查找工作,线程不安全
- `Vector``List` 的古老实现类,底层使用` Object[ ]` 存储,线程安全的。
### 1.2.2. Arraylist 与 LinkedList 区别?
@ -295,23 +295,23 @@ Output
### 1.3.3. 比较 HashSet、LinkedHashSet 和 TreeSet 三者的异同
HashSet 是 Set 接口的主要实现类 HashSet 的底层是 HashMap线程不安全的可以存储 null 值;
`HashSet``Set` 接口的主要实现类 `HashSet` 的底层是 `HashMap`,线程不安全的,可以存储 null 值;
LinkedHashSet 是 HashSet 的子类,能够按照添加的顺序遍历;
`LinkedHashSet``HashSet` 的子类,能够按照添加的顺序遍历;
TreeSet 底层使用红黑树,能够按照添加元素的顺序进行遍历,排序的方式有自然排序和定制排序。
`TreeSet` 底层使用红黑树,能够按照添加元素的顺序进行遍历,排序的方式有自然排序和定制排序。
## 1.4. Map 接口
### 1.4.1. HashMap 和 Hashtable 的区别
1. **线程是否安全:** HashMap 是非线程安全的HashTable 是线程安全的,因为 HashTable 内部的方法基本都经过`synchronized` 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);
2. **效率:** 因为线程安全的问题HashMap 要比 HashTable 效率高一点。另外HashTable 基本被淘汰,不要在代码中使用它;
3. **对 Null key 和 Null value 的支持:** HashMap 可以存储 null 的 key 和 value但 null 作为键只能有一个null 作为值可以有多个HashTable 不允许有 null 键和 null 值,否则会抛出 NullPointerException。
4. **初始容量大小和每次扩充容量大小的不同 ** ① 创建时如果不指定容量初始值Hashtable 默认的初始大小为 11之后每次扩充容量变为原来的 2n+1。HashMap 默认的初始化大小为 16。之后每次扩充容量变为原来的 2 倍。② 创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为 2 的幂次方大小HashMap 中的`tableSizeFor()`方法保证,下面给出了源代码)。也就是说 HashMap 总是使用 2 的幂作为哈希表的大小,后面会介绍到为什么是 2 的幂次方。
5. **底层数据结构:** JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8将链表转换成红黑树前会判断如果当前数组的长度小于 64那么会选择先进行数组扩容而不是转换为红黑树将链表转化为红黑树以减少搜索时间。Hashtable 没有这样的机制。
1. **线程是否安全:** `HashMap` 是非线程安全的,`HashTable` 是线程安全的,因为 `HashTable` 内部的方法基本都经过`synchronized` 修饰。(如果你要保证线程安全的话就使用 `ConcurrentHashMap` 吧!);
2. **效率:** 因为线程安全的问题,`HashMap` 要比 `HashTable` 效率高一点。另外,`HashTable` 基本被淘汰,不要在代码中使用它;
3. **对 Null key 和 Null value 的支持:** `HashMap` 可以存储 null 的 key 和 value但 null 作为键只能有一个null 作为值可以有多个HashTable 不允许有 null 键和 null 值,否则会抛出 `NullPointerException`
4. **初始容量大小和每次扩充容量大小的不同 ** ① 创建时如果不指定容量初始值,`Hashtable` 默认的初始大小为 11之后每次扩充容量变为原来的 2n+1。`HashMap` 默认的初始化大小为 16。之后每次扩充容量变为原来的 2 倍。② 创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 `HashMap` 会将其扩充为 2 的幂次方大小(`HashMap` 中的`tableSizeFor()`方法保证,下面给出了源代码)。也就是说 `HashMap` 总是使用 2 的幂作为哈希表的大小,后面会介绍到为什么是 2 的幂次方。
5. **底层数据结构:** JDK1.8 以后的 `HashMap` 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8将链表转换成红黑树前会判断如果当前数组的长度小于 64那么会选择先进行数组扩容而不是转换为红黑树将链表转化为红黑树以减少搜索时间。Hashtable 没有这样的机制。
**HashMap 中带有初始容量的构造函数:**
**`HashMap` 中带有初始容量的构造函数:**
```java
public HashMap(int initialCapacity, float loadFactor) {
@ -331,7 +331,7 @@ TreeSet 底层使用红黑树,能够按照添加元素的顺序进行遍历,
}
```
下面这个方法保证了 HashMap 总是使用 2 的幂作为哈希表的大小。
下面这个方法保证了 `HashMap` 总是使用 2 的幂作为哈希表的大小。
```java
/**
@ -350,14 +350,14 @@ TreeSet 底层使用红黑树,能够按照添加元素的顺序进行遍历,
### 1.4.2. HashMap 和 HashSet 区别
如果你看过 `HashSet` 源码的话就应该知道HashSet 底层就是基于 HashMap 实现的。HashSet 的源码非常非常少,因为除了 `clone()``writeObject()``readObject()`是 HashSet 自己不得不实现之外,其他方法都是直接调用 HashMap 中的方法。
如果你看过 `HashSet` 源码的话就应该知道:`HashSet` 底层就是基于 `HashMap` 实现的。(`HashSet` 的源码非常非常少,因为除了 `clone()``writeObject()``readObject()``HashSet` 自己不得不实现之外,其他方法都是直接调用 `HashMap` 中的方法。
| HashMap | HashSet |
| :--------------------------------: | :----------------------------------------------------------: |
| 实现了 Map 接口 | 实现 Set 接口 |
| `HashMap` | `HashSet` |
| :------------------------------------: | :----------------------------------------------------------: |
| 实现了 `Map` 接口 | 实现 `Set` 接口 |
| 存储键值对 | 仅存储对象 |
| 调用 `put()`向 map 中添加元素 | 调用 `add()`方法向 Set 中添加元素 |
| HashMap 使用键Key计算 Hashcode | HashSet 使用成员对象来计算 hashcode 值,对于两个对象来说 hashcode 可能相同,所以 equals()方法用来判断对象的相等性, |
| 调用 `put()`向 map 中添加元素 | 调用 `add()`方法向 `Set` 中添加元素 |
| `HashMap` 使用键Key计算 `hashcode` | `HashSet` 使用成员对象来计算 `hashcode` 值,对于两个对象来说 `hashcode` 可能相同,所以` equals()`方法用来判断对象的相等性 |
### 1.4.3. HashMap 和 TreeMap 区别
@ -429,15 +429,17 @@ TreeMap<Person, String> treeMap = new TreeMap<>((person1, person2) -> {
### 1.4.4. HashSet 如何检查重复
当你把对象加入`HashSet`HashSet 会先计算对象的`hashcode`值来判断对象加入的位置,同时也会与其他加入的对象的 hashcode 值作比较,如果没有相符的 hashcodeHashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用`equals()`方法来检查 hashcode 相等的对象是否真的相同。如果两者相同HashSet 就不会让加入操作成功。(摘自我的 Java 启蒙书《Head fist java》第二版
以下内容摘自我的 Java 启蒙书《Head fist java》第二版
**hashCode()与 equals()的相关规定:**
当你把对象加入`HashSet`时,`HashSet` 会先计算对象的`hashcode`值来判断对象加入的位置,同时也会与其他加入的对象的 `hashcode` 值作比较,如果没有相符的 `hashcode``HashSet` 会假设对象没有重复出现。但是如果发现有相同 `hashcode` 值的对象,这时会调用`equals()`方法来检查 `hashcode` 相等的对象是否真的相同。如果两者相同,`HashSet` 就不会让加入操作成功。
1. 如果两个对象相等,则 hashcode 一定也是相同的
2. 两个对象相等,对两个 equals 方法返回 true
3. 两个对象有相同的 hashcode 值,它们也不一定是相等的
4. 综上equals 方法被覆盖过,则 hashCode 方法也必须被覆盖
5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。
**`hashCode()``equals()` 的相关规定:**
1. 如果两个对象相等,则 `hashcode` 一定也是相同的
2. 两个对象相等,对两个 `equals()` 方法返回 true
3. 两个对象有相同的 `hashcode` 值,它们也不一定是相等的
4. 综上,`equals()` 方法被覆盖过,则 `hashCode()` 方法也必须被覆盖
5. `hashCode() `的默认行为是对堆上的对象产生独特值。如果没有重写 `hashCode()`,则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。
**==与 equals 的区别**
@ -516,10 +518,10 @@ static int hash(int h) {
### 1.4.9. ConcurrentHashMap 和 Hashtable 的区别
ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。
`ConcurrentHashMap``Hashtable` 的区别主要体现在实现线程安全的方式上不同。
- **底层数据结构:** JDK1.7 的 ConcurrentHashMap 底层采用 **分段的数组+链表** 实现JDK1.8 采用的数据结构跟 HashMap1.8 的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 **数组+链表** 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
- **实现线程安全的方式(重要):****在 JDK1.7 的时候,ConcurrentHashMap分段锁** 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 **到了 JDK1.8 的时候已经摒弃了 Segment 的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。JDK1.6 以后 对 synchronized 锁做了很多优化)** 整个看起来就像是优化过且线程安全的 HashMap虽然在 JDK1.8 中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② **Hashtable(同一把锁)** :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get竞争会越来越激烈效率越低。
- **底层数据结构:** JDK1.7 的 `ConcurrentHashMap` 底层采用 **分段的数组+链表** 实现JDK1.8 采用的数据结构跟 `HashMap1.8` 的结构一样,数组+链表/红黑二叉树。`Hashtable` 和 JDK1.8 之前的 `HashMap` 的底层数据结构类似都是采用 **数组+链表** 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
- **实现线程安全的方式(重要):****在 JDK1.7 的时候,`ConcurrentHashMap`(分段锁)** 对整个桶数组进行了分割分段(`Segment`),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 **到了 JDK1.8 的时候已经摒弃了 `Segment` 的概念,而是直接用 `Node` 数组+链表+红黑树的数据结构来实现,并发控制使用 `synchronized` 和 CAS 来操作。JDK1.6 以后 对 `synchronized` 锁做了很多优化)** 整个看起来就像是优化过且线程安全的 `HashMap`,虽然在 JDK1.8 中还能看到 `Segment` 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② **`Hashtable`(同一把锁)** :使用 `synchronized` 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get竞争会越来越激烈效率越低。
**两者的对比图:**
@ -547,22 +549,22 @@ JDK1.8 的 `ConcurrentHashMap` 不在是 **Segment 数组 + HashEntry 数组 +
首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
**ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成**。
**`ConcurrentHashMap` 是由 `Segment` 数组结构和 `HashEntry` 数组结构组成**。
Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁扮演锁的角色。HashEntry 用于存储键值对数据。
Segment 实现了 `ReentrantLock`,所以 `Segment` 是一种可重入锁,扮演锁的角色。`HashEntry` 用于存储键值对数据。
```java
static class Segment<K,V> extends ReentrantLock implements Serializable {
}
```
一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和 HashMap 类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个 HashEntry 数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 的锁。
一个 `ConcurrentHashMap` 里包含一个 `Segment` 数组。`Segment` 的结构和 `HashMap` 类似,是一种数组和链表结构,一个 `Segment` 包含一个 `HashEntry` 数组,每个 `HashEntry` 是一个链表结构的元素,每个 `Segment` 守护着一个 `HashEntry` 数组里的元素,当对 `HashEntry` 数组的数据进行修改时,必须首先获得对应的 `Segment` 的锁。
#### 1.4.10.2. JDK1.8 (上面有示意图)
ConcurrentHashMap 取消了 Segment 分段锁,采用 CAS 和 synchronized 来保证并发安全。数据结构跟 HashMap1.8 的结构类似,数组+链表/红黑二叉树。Java 8 在链表长度超过一定阈值8时将链表寻址时间复杂度为 O(N))转换为红黑树(寻址时间复杂度为 O(log(N))
`ConcurrentHashMap` 取消了 `Segment` 分段锁,采用 CAS 和 `synchronized` 来保证并发安全。数据结构跟 HashMap1.8 的结构类似,数组+链表/红黑二叉树。Java 8 在链表长度超过一定阈值8时将链表寻址时间复杂度为 O(N))转换为红黑树(寻址时间复杂度为 O(log(N))
synchronized 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,效率又提升 N 倍。
`synchronized` 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,效率又提升 N 倍。
## 1.5. Collections 工具类