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:
commit
8fe56a64af
@ -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 入门(基础概念与常识)
|
||||
@ -186,6 +184,7 @@ Java 语言既具有编译型语言的特征,也具有解释型语言的特征
|
||||
1. 形式上: 字符常量是单引号引起的一个字符; 字符串常量是双引号引起的 0 个或若干个字符
|
||||
2. 含义上: 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)
|
||||
3. 占内存大小 字符常量只占 2 个字节; 字符串常量占若干个字节 (**注意: char 在 Java 中占两个字节**),
|
||||
|
||||
> 字符封装类 `Character` 有一个成员常量 `Character.SIZE` 值为 16,单位是`bits`,该值除以 8(`1byte=8bits`)后就可以得到 2 个字节
|
||||
|
||||
> java 编程思想第四版:2.2.2 节
|
||||
@ -477,9 +476,6 @@ public native int hashCode();
|
||||
|
||||
我们刚刚也提到了 `HashSet`,如果 `HashSet` 在对比的时候,同样的 hashcode 有多个对象,它会使用 `equals()` 来判断是否真的相同。也就是说 `hashcode` 只是用来缩小查找成本。
|
||||
|
||||
|
||||
|
||||
|
||||
更多关于 `hashcode()` 和 `equals()` 的内容可以查看:[Java hashCode() 和 equals()的若干问题解答](https://www.cnblogs.com/skywang12345/p/3324958.html)
|
||||
|
||||
### 1.3. 基本数据类型
|
||||
@ -570,6 +566,7 @@ private static class CharacterCache {
|
||||
```
|
||||
|
||||
**应用场景:**
|
||||
|
||||
1. Integer i1=40;Java 在编译的时候会直接将代码封装成 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) ):
|
||||
|
||||
- “两同”即方法名相同、形参列表相同;
|
||||
@ -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 异常类层次结构图
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
<p style="font-size:13px;text-align:right">图片来自:https://simplesnippets.tech/exception-handling-in-java-part-1/</p>
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
<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(错误):是程序无法处理的错误**,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,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 操作的代码。
|
||||
|
||||

|
||||
|
||||
除了`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。
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 278 KiB |
Binary file not shown.
Before Width: | Height: | Size: 128 KiB |
@ -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 值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。但是如果发现有相同 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 工具类
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user