1
0
mirror of https://github.com/Snailclimb/JavaGuide synced 2025-08-01 16:28:03 +08:00

update:java基础和集合部分的面试问题标注重点

This commit is contained in:
Guide 2025-07-27 11:31:30 +08:00
parent 3973ee5ef3
commit 3dc6b21318
9 changed files with 64 additions and 65 deletions

View File

@ -18,7 +18,7 @@ footer: |-
## 关于网站
JavaGuide 已经持续维护 6 年多了,累计提交了 **5600+** commit ,共有 **550+** 多位贡献者共同参与维护和完善。真心希望能够把这个项目做好,真正能够帮助到有需要的朋友!
JavaGuide 已经持续维护 6 年多了,累计提交了接近 **6000** commit ,共有 **570+** 多位贡献者共同参与维护和完善。真心希望能够把这个项目做好,真正能够帮助到有需要的朋友!
如果觉得 JavaGuide 的内容对你有帮助的话,还请点个免费的 Star绝不强制点 Star觉得内容不错有收获再点赞就好这是对我最大的鼓励感谢各位一路同行共勉传送门[GitHub](https://github.com/Snailclimb/JavaGuide) | [Gitee](https://gitee.com/SnailClimb/JavaGuide)。

View File

@ -361,7 +361,7 @@ Linux 系统是一个多用户多任务的分时操作系统,任何一个要
### 其他
- `sudo + 其他命令`:以系统管理者的身份执行指令,也就是说,经由 sudo 所执行的指令就好像是 root 亲自执行。
- `grep 要搜索的字符串 要搜索的文件 --color`:搜索命令,--color 代表高亮显示
- `grep [选项] "搜索内容" 文件路径`:非常强大且常用的文本搜索命令,它可以根据指定的字符串或正则表达式,在文件或命令输出中进行匹配查找,适用于日志分析、文本过滤、快速定位等多种场景。示例:忽略大小写搜索 syslog 中所有包含 error 的行:`grep -i "error" /var/log/syslog`,查找所有与 java 相关的进程:`ps -ef | grep "java"`
- `kill -9 进程的pid`:杀死进程(-9 表示强制终止)先用 ps 查找进程,然后用 kill 杀掉。
- `shutdown``shutdown -h now`:指定现在立即关机;`shutdown +5 "System will shutdown after 5 minutes"`:指定 5 分钟后关机,同时送出警告信息给登入用户。
- `reboot``reboot`:重开机。`reboot -w`:做个重开机的模拟(只有纪录并不会真的重开机)。

View File

@ -44,7 +44,7 @@ head:
除了 Java SE 和 Java EE还有一个 Java MEJava PlatformMicro Edition。Java ME 是 Java 的微型版本主要用于开发嵌入式消费电子设备的应用程序例如手机、PDA、机顶盒、冰箱、空调等。Java ME 无需重点关注,知道有这个东西就好了,现在已经用不上了。
### JVM vs JDK vs JRE
### ⭐️JVM vs JDK vs JRE
#### JVM
@ -87,7 +87,7 @@ JRE 是运行已编译 Java 程序所需的环境,主要包含以下两个部
定制的、模块化的 Java 运行时映像有助于简化 Java 应用的部署和节省内存并增强安全性和可维护性。这对于满足现代应用程序架构的需求,如虚拟化、容器化、微服务和云原生开发,是非常重要的。
### 什么是字节码?采用字节码的好处是什么?
### ⭐️什么是字节码?采用字节码的好处是什么?
在 Java 中JVM 可以理解的代码就叫做字节码(即扩展名为 `.class` 的文件它不面向任何特定的处理器只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以, Java 程序运行时相对来说还是高效的(不过,和 C、 C++RustGo 等语言还是有一定差距的而且由于字节码并不针对一种特定的机器因此Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。
@ -114,7 +114,7 @@ JDK、JRE、JVM、JIT 这四者的关系如下图所示。
![JVM 的大致结构模型](https://oss.javaguide.cn/github/javaguide/java/basis/jvm-rough-structure-model.png)
### 为什么说 Java 语言“编译与解释并存”?
### ⭐️为什么说 Java 语言“编译与解释并存”?
其实这个问题我们讲字节码的时候已经提到过,因为比较重要,所以我们这里再提一下。
@ -282,7 +282,7 @@ Java 中的注释有三种:
官方文档:[https://docs.oracle.com/javase/tutorial/java/nutsandbolts/\_keywords.html](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html)
### 自增自减运算符
### ⭐️自增自减运算符
在写代码的过程中,常见的一种情况是需要某个整数类型变量增加 1 或减少 1。Java 提供了自增运算符 (`++`) 和自减运算符 (`--`) 来简化这种操作。
@ -305,7 +305,7 @@ int e = --d;
答案:`a = 11``b = 9``c = 10``d = 10``e = 10`
### 移位运算符
### ⭐️移位运算符
移位运算符是最基本的运算符之一,几乎每种编程语言都包含这一运算符。移位操作中,被操作的数据被视为二进制数,移位就是将其向左或向右移动若干位的运算。
@ -442,7 +442,7 @@ xixi
haha
```
## 基本数据类型
## ⭐️基本数据类型
### Java 中的几种基本数据类型了解么?
@ -736,7 +736,7 @@ System.out.println(l + 1 == Long.MIN_VALUE); // true
## 变量
### 成员变量与局部变量的区别?
### ⭐️成员变量与局部变量的区别?
- **语法形式**:从语法形式上看,成员变量是属于类的,而局部变量是在代码块或方法中定义的变量或是方法的参数;成员变量可以被 `public`,`private`,`static` 等修饰符所修饰,而局部变量不能被访问控制修饰符及 `static` 所修饰;但是,成员变量和局部变量都能被 `final` 所修饰。
- **存储方式**:从变量在内存中的存储方式来看,如果成员变量是使用 `static` 修饰的,那么这个成员变量是属于类的,如果没有使用 `static` 修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。
@ -914,7 +914,7 @@ public class Example {
}
```
### 静态方法和实例方法有何不同?
### ⭐️静态方法和实例方法有何不同?
**1、调用方式**
@ -947,7 +947,7 @@ public class Person {
静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),不允许访问实例成员(即实例成员变量和实例方法),而实例方法不存在这个限制。
### 重载和重写有什么区别?
### ⭐️重载和重写有什么区别?
> 重载就是同样的一个方法能够根据输入数据的不同,做出不同的处理
>
@ -984,14 +984,13 @@ public class Person {
综上:**重写就是子类对父类方法的重新改造,外部样子不能改变,内部逻辑可以改变。**
| 区别点 | 重载方法 | 重写方法 |
| :--------- | :------- | :--------------------------------------------------------------- |
| 发生范围 | 同一个类 | 子类 |
| 参数列表 | 必须修改 | 一定不能修改 |
| 返回类型 | 可修改 | 子类方法返回值类型应比父类方法返回值类型更小或相等 |
| 异常 | 可修改 | 子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等; |
| 访问修饰符 | 可修改 | 一定不能做更严格的限制(可以降低限制) |
| 发生阶段 | 编译期 | 运行期 |
| 区别点 | 重载 (Overloading) | 重写 (Overriding) |
| -------------- | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
| **发生范围** | 同一个类中。 | 父类与子类之间(存在继承关系)。 |
| **方法签名** | 方法名**必须相同**,但**参数列表必须不同**(参数的类型、个数或顺序至少有一项不同)。 | 方法名、参数列表**必须完全相同**。 |
| **返回类型** | 与返回值类型**无关**,可以任意修改。 | 子类方法的返回类型必须与父类方法的返回类型**相同**,或者是其**子类**。 |
| **访问修饰符** | 与访问修饰符**无关**,可以任意修改。 | 子类方法的访问权限**不能低于**父类方法的访问权限。public > protected > default > private |
| **绑定时期** | 编译时绑定或称静态绑定 | 运行时绑定 (Run-time Binding) 或称动态绑定 |
**方法的重写要遵循“两同两小一大”**(以下内容摘录自《疯狂 Java 讲义》,[issue#892](https://github.com/Snailclimb/JavaGuide/issues/892)

View File

@ -16,7 +16,7 @@ head:
## 面向对象基础
### 面向对象和面向过程的区别
### ⭐️面向对象和面向过程的区别
面向过程编程Procedural-Oriented ProgrammingPOP和面向对象编程Object-Oriented ProgrammingOOP是两种常见的编程范式两者的主要区别在于解决问题的方式不同
@ -104,7 +104,7 @@ new 运算符new 创建对象实例(对象实例在堆内存中),对象
- 一个对象引用可以指向 0 个或 1 个对象(一根绳子可以不系气球,也可以系一个气球);
- 一个对象可以有 n 个引用指向它(可以用 n 条绳子系住一个气球)。
### 对象的相等和引用相等的区别
### ⭐️对象的相等和引用相等的区别
- 对象的相等一般比较的是内存中存放的内容是否相等。
- 引用相等一般比较的是他们指向的内存地址是否相等。
@ -156,7 +156,7 @@ true
构造方法**不能被重写override**,但**可以被重载overload**。因此,一个类中可以有多个构造方法,这些构造方法可以具有不同的参数列表,以提供不同的对象初始化方式。
### 面向对象三大特征
### ⭐️面向对象三大特征
#### 封装
@ -210,7 +210,7 @@ public class Student {
- 多态不能调用“只在子类存在但在父类不存在”的方法;
- 如果子类重写了父类的方法,真正执行的是子类重写的方法,如果子类没有重写父类的方法,执行的是父类的方法。
### 接口和抽象类有什么共同点和区别?
### ⭐️接口和抽象类有什么共同点和区别?
#### 接口和抽象类的共同点
@ -363,7 +363,7 @@ System.out.println(person1.getAddress() == person1Copy.getAddress());
![shallow&deep-copy](https://oss.javaguide.cn/github/javaguide/java/basis/shallow&deep-copy.png)
## Object
## ⭐️Object
### Object 类的常见方法有哪些?
@ -551,7 +551,7 @@ public native int hashCode();
## String
### String、StringBuffer、StringBuilder 的区别?
### ⭐️String、StringBuffer、StringBuilder 的区别?
**可变性**
@ -589,7 +589,7 @@ abstract class AbstractStringBuilder implements Appendable, CharSequence {
- 单线程操作字符串缓冲区下操作大量数据: 适用 `StringBuilder`
- 多线程操作字符串缓冲区下操作大量数据: 适用 `StringBuffer`
### String 为什么是不可变的?
### ⭐️String 为什么是不可变的?
`String` 类中使用 `final` 关键字修饰字符数组来保存字符串,~~所以`String` 对象是不可变的。~~
@ -636,7 +636,7 @@ public final class String implements java.io.Serializable, Comparable<String>, C
>
> 这是官方的介绍:<https://openjdk.java.net/jeps/254>
### 字符串拼接用“+” 还是 StringBuilder?
### ⭐️字符串拼接用“+” 还是 StringBuilder?
Java 语言本身并不支持运算符重载,“+”和“+=”是专门为 String 类重载过的运算符,也是 Java 中仅有的两个重载过的运算符。
@ -689,7 +689,7 @@ System.out.println(s);
`String` 中的 `equals` 方法是被重写过的,比较的是 String 字符串的值是否相等。 `Object``equals` 方法是比较的对象的内存地址。
### 字符串常量池的作用了解吗?
### ⭐️字符串常量池的作用了解吗?
**字符串常量池** 是 JVM 为了提升性能和减少内存消耗针对字符串String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
@ -704,7 +704,7 @@ System.out.println(aa==bb); // true
更多关于字符串常量池的介绍可以看一下 [Java 内存区域详解](https://javaguide.cn/java/jvm/memory-area.html) 这篇文章。
### String s1 = new String("abc");这句话创建了几个字符串对象?
### ⭐️String s1 = new String("abc");这句话创建了几个字符串对象?
先说答案:会创建 1 或 2 个字符串对象。

View File

@ -27,7 +27,7 @@ head:
- **`Exception`** :程序本身可以处理的异常,可以通过 `catch` 来进行捕获。`Exception` 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。
- **`Error`**`Error` 属于程序无法处理的错误 ~~我们没办法通过 `catch` 来进行捕获~~不建议通过`catch`捕获 。例如 Java 虚拟机运行错误(`Virtual MachineError`)、虚拟机内存不够错误(`OutOfMemoryError`)、类定义错误(`NoClassDefFoundError`)等 。这些异常发生时Java 虚拟机JVM一般会选择线程终止。
### Checked Exception 和 Unchecked Exception 有什么区别?
### ⭐️Checked Exception 和 Unchecked Exception 有什么区别?
**Checked Exception** 即 受检查异常 Java 代码在编译过程中,如果受检查异常没有被 `catch`或者`throws` 关键字处理的话,就没办法通过编译。
@ -205,7 +205,7 @@ catch (IOException e) {
}
```
### 异常使用有哪些需要注意的地方?
### ⭐️异常使用有哪些需要注意的地方?
- 不要把异常定义为静态变量,因为这样会导致异常栈信息错乱。每次手动抛出异常,我们都需要手动 new 一个异常对象抛出。
- 抛出的异常信息一定要有意义。
@ -317,9 +317,9 @@ printArray( stringArray );
- 构建集合工具类(参考 `Collections` 中的 `sort`, `binarySearch` 方法)。
- ……
## 反射
## ⭐️反射
关于反射的详细解读,请看这篇文章 [Java 反射机制详解](./reflection.md) 。
关于反射的详细解读,请看这篇文章 [Java 反射机制详解](https://javaguide.cn/java/basis/reflection.html) 。
### 什么是反射?
@ -413,9 +413,9 @@ JDK 提供了很多内置的注解(比如 `@Override`、`@Deprecated`),同
- **编译期直接扫描**:编译器在编译 Java 代码的时候扫描对应的注解并处理,比如某个方法使用`@Override` 注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。
- **运行期通过反射处理**:像框架中自带的注解(比如 Spring 框架的 `@Value``@Component`)都是通过反射来进行处理的。
## SPI
## ⭐️SPI
关于 SPI 的详细解读,请看这篇文章 [Java SPI 机制详解](./spi.md) 。
关于 SPI 的详细解读,请看这篇文章 [Java SPI 机制详解](https://javaguide.cn/java/basis/spi.html) 。
### 何谓 SPI?
@ -449,9 +449,9 @@ SPI 将服务接口和具体的服务实现分离开来,将服务调用方和
- 需要遍历加载所有的实现类,不能做到按需加载,这样效率还是相对较低的。
- 当多个 `ServiceLoader` 同时 `load` 时,会有并发问题。
## 序列化和反序列化
## ⭐️序列化和反序列化
关于序列化和反序列化的详细解读,请看这篇文章 [Java 序列化详解](./serialization.md) ,里面涉及到的知识点和面试题更全面。
关于序列化和反序列化的详细解读,请看这篇文章 [Java 序列化详解](https://javaguide.cn/java/basis/serialization.html) ,里面涉及到的知识点和面试题更全面。
### 什么是序列化?什么是反序列化?
@ -526,9 +526,9 @@ JDK 自带的序列化方式一般不会用 ,因为序列化效率低并且存
关于 I/O 的详细解读,请看下面这几篇文章,里面涉及到的知识点和面试题更全面。
- [Java IO 基础知识总结](../io/io-basis.md)
- [Java IO 设计模式总结](../io/io-design-patterns.md)
- [Java IO 模型详解](../io/io-model.md)
- [Java IO 基础知识总结](https://javaguide.cn/java/io/io-basis.html)
- [Java IO 设计模式总结](https://javaguide.cn/java/io/io-design-patterns.html)
- [Java IO 模型详解](https://javaguide.cn/java/io/io-model.html)
### Java IO 流了解吗?
@ -550,11 +550,11 @@ Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生出来
### Java IO 中的设计模式有哪些?
参考答案:[Java IO 设计模式总结](../io/io-design-patterns.md)
参考答案:[Java IO 设计模式总结](https://javaguide.cn/java/io/io-design-patterns.html)
### BIO、NIO 和 AIO 的区别?
参考答案:[Java IO 模型详解](../io/io-model.md)
参考答案:[Java IO 模型详解](https://javaguide.cn/java/io/io-model.html)
## 语法糖

View File

@ -28,7 +28,7 @@ Java 集合框架如下图所示:
注:图中只列举了主要的继承派生关系,并没有列举所有关系。比方省略了`AbstractList`, `NavigableSet`等抽象类以及其他的一些辅助类,如想深入了解,可自行查看源码。
### 说说 List, Set, Queue, Map 四者的区别?
### ⭐️说说 List, Set, Queue, Map 四者的区别?
- `List`(对付顺序的好帮手): 存储的元素是有序的、可重复的。
- `Set`(注重独一无二的性质): 存储的元素不可重复的。
@ -79,7 +79,7 @@ Java 集合框架如下图所示:
## List
### ArrayList 和 Array数组的区别
### ⭐️ArrayList 和 Array数组的区别
`ArrayList` 内部基于动态数组实现,比 `Array`(静态数组) 使用起来更加灵活:
@ -154,7 +154,7 @@ System.out.println(listOfStrings);
[null, java]
```
### ArrayList 插入和删除元素的时间复杂度?
### ⭐️ArrayList 插入和删除元素的时间复杂度?
对于插入:
@ -188,13 +188,13 @@ System.out.println(listOfStrings);
0 1 2 3 4 5 6 7 8 9
```
### LinkedList 插入和删除元素的时间复杂度?
### ⭐️LinkedList 插入和删除元素的时间复杂度?
- 头部插入/删除:只需要修改头结点的指针即可完成插入/删除操作,因此时间复杂度为 O(1)。
- 尾部插入/删除:只需要修改尾结点的指针即可完成插入/删除操作,因此时间复杂度为 O(1)。
- 指定位置插入/删除:需要先移动到指定位置,再修改指定节点的指针完成插入/删除,不过由于有头尾指针,可以从较近的指针出发,因此需要遍历平均 n/4 个元素,时间复杂度为 O(n)。
这里简单列举一个例子:假如我们要删除节点 9 的话,需要先遍历链表找到该节点。然后,再执行相应节点指针指向的更改,具体的源码可以参考:[LinkedList 源码分析](./linkedlist-source-code.md) 。
这里简单列举一个例子:假如我们要删除节点 9 的话,需要先遍历链表找到该节点。然后,再执行相应节点指针指向的更改,具体的源码可以参考:[LinkedList 源码分析](https://javaguide.cn/java/collection/linkedlist-source-code.html) 。
![unlink 方法逻辑](https://oss.javaguide.cn/github/javaguide/java/collection/linkedlist-unlink.jpg)
@ -202,7 +202,7 @@ System.out.println(listOfStrings);
`RandomAccess` 是一个标记接口,用来表明实现该接口的类支持随机访问(即可以通过索引快速访问元素)。由于 `LinkedList` 底层数据结构是链表,内存地址不连续,只能通过指针来定位,不支持随机快速访问,所以不能实现 `RandomAccess` 接口。
### ArrayList 与 LinkedList 区别?
### ⭐️ArrayList 与 LinkedList 区别?
- **是否保证线程安全:** `ArrayList``LinkedList` 都是不同步的,也就是不保证线程安全;
- **底层数据结构:** `ArrayList` 底层使用的是 **`Object` 数组**`LinkedList` 底层使用的是 **双向链表** 数据结构JDK1.6 之前为循环链表JDK1.7 取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!)
@ -251,11 +251,11 @@ public interface RandomAccess {
`ArrayList` 实现了 `RandomAccess` 接口, 而 `LinkedList` 没有实现。为什么呢?我觉得还是和底层数据结构有关!`ArrayList` 底层是数组,而 `LinkedList` 底层是链表。数组天然支持随机访问,时间复杂度为 O(1),所以称为快速随机访问。链表需要遍历到特定位置才能访问特定位置的元素,时间复杂度为 O(n),所以不支持快速随机访问。`ArrayList` 实现了 `RandomAccess` 接口,就表明了他具有快速随机访问功能。 `RandomAccess` 接口只是标识,并不是说 `ArrayList` 实现 `RandomAccess` 接口才具有快速随机访问功能的!
### 说一说 ArrayList 的扩容机制吧
### ⭐️说一说 ArrayList 的扩容机制吧
详见笔主的这篇文章: [ArrayList 扩容机制分析](https://javaguide.cn/java/collection/arraylist-source-code.html#_3-1-%E5%85%88%E4%BB%8E-arraylist-%E7%9A%84%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0%E8%AF%B4%E8%B5%B7)。
详见笔主的这篇文章: [ArrayList 扩容机制分析](https://javaguide.cn/java/collection/arraylist-source-code.html#arraylist-扩容机制分析)。
### 说说集合中的 fail-fast 和 fail-safe 是什么
### ⭐️集合中的 fail-fast 和 fail-safe 是什么?
关于`fail-fast`引用`medium`中一篇文章关于`fail-fast``fail-safe`的说法:
@ -579,7 +579,7 @@ Java 中常用的阻塞队列实现类有以下几种:
日常开发中,这些队列使用的其实都不多,了解即可。
### ArrayBlockingQueue 和 LinkedBlockingQueue 有什么区别?
### ⭐️ArrayBlockingQueue 和 LinkedBlockingQueue 有什么区别?
`ArrayBlockingQueue``LinkedBlockingQueue` 是 Java 并发包中常用的两种阻塞队列实现,它们都是线程安全的。不过,不过它们之间也存在下面这些区别:

View File

@ -16,7 +16,7 @@ head:
## Map重要
### HashMap 和 Hashtable 的区别
### ⭐️HashMap 和 Hashtable 的区别
- **线程是否安全:** `HashMap` 是非线程安全的,`Hashtable` 是线程安全的,因为 `Hashtable` 内部的方法基本都经过`synchronized` 修饰。(如果你要保证线程安全的话就使用 `ConcurrentHashMap` 吧!);
- **效率:** 因为线程安全的问题,`HashMap` 要比 `Hashtable` 效率高一点。另外,`Hashtable` 基本被淘汰,不要在代码中使用它;
@ -73,7 +73,7 @@ static final int tableSizeFor(int cap) {
| 调用 `put()`向 map 中添加元素 | 调用 `add()`方法向 `Set` 中添加元素 |
| `HashMap` 使用键Key计算 `hashcode` | `HashSet` 使用成员对象来计算 `hashcode` 值,对于两个对象来说 `hashcode` 可能相同,所以`equals()`方法用来判断对象的相等性 |
### HashMap 和 TreeMap 区别
### ⭐️HashMap 和 TreeMap 区别
`TreeMap``HashMap` 都继承自`AbstractMap` ,但是需要注意的是`TreeMap`它还实现了`NavigableMap`接口和`SortedMap` 接口。
@ -179,7 +179,7 @@ final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
也就是说,在 JDK1.8 中,实际上无论`HashSet`中是否已经存在了某元素,`HashSet`都会直接插入,只是会在`add()`方法的返回值处告诉我们插入前是否存在相同元素。
### HashMap 的底层实现
### ⭐️HashMap 的底层实现
#### JDK1.8 之前
@ -297,7 +297,7 @@ final void treeifyBin(Node<K,V>[] tab, int hash) {
将链表转换成红黑树前会判断,如果当前数组的长度小于 64那么会选择先进行数组扩容而不是转换为红黑树。
### HashMap 的长度为什么是 2 的幂次方
### ⭐️HashMap 的长度为什么是 2 的幂次方
为了让 `HashMap` 存取高效并减少碰撞,我们需要确保数据尽量均匀分布。哈希值在 Java 中通常使用 `int` 表示,其范围是 `-2147483648 ~ 2147483647`前后加起来大概 40 亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但是,问题是一个 40 亿长度的数组,内存是放不下的。所以,这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。
@ -349,7 +349,7 @@ index = 00001100 (12)
2. 可以更好地保证哈希值的均匀分布:扩容之后,在旧数组元素 hash 值比较均匀的情况下,新数组元素也会被分配的比较均匀,最好的情况是会有一半在新数组的前半部分,一半在新数组后半部分。
3. 扩容机制变得简单和高效:扩容后只需检查哈希值高位的变化来决定元素的新位置,要么位置不变(高位为 0要么就是移动到新位置高位为 1原索引位置+原容量)。
### HashMap 多线程操作导致死循环问题
### ⭐️HashMap 多线程操作导致死循环问题
JDK1.7 及之前版本的 `HashMap` 在多线程环境下扩容操作可能存在死循环问题,这是由于当一个桶位中有多个元素需要进行扩容时,多个线程同时对链表进行操作,头插法可能会导致链表中的节点指向错误的位置,从而形成一个环形链表,进而使得查询元素的操作陷入死循环无法结束。
@ -357,7 +357,7 @@ JDK1.7 及之前版本的 `HashMap` 在多线程环境下扩容操作可能存
一般面试中这样介绍就差不多,不需要记各种细节,个人觉得也没必要记。如果想要详细了解 `HashMap` 扩容导致死循环问题,可以看看耗子叔的这篇文章:[Java HashMap 的死循环](https://coolshell.cn/articles/9606.html)。
### HashMap 为什么线程不安全?
### ⭐️HashMap 为什么线程不安全?
JDK1.7 及之前版本,在多线程环境下,`HashMap` 扩容时会造成死循环和数据丢失的问题。
@ -441,7 +441,7 @@ Test.lambda avgt 5 1551065180.000 ± 19164407.426 ns/op
Test.parallelStream avgt 5 186345456.667 ± 3210435.590 ns/op
```
### ConcurrentHashMap 和 Hashtable 的区别
### ⭐️ConcurrentHashMap 和 Hashtable 的区别
`ConcurrentHashMap``Hashtable` 的区别主要体现在实现线程安全的方式上不同。
@ -489,7 +489,7 @@ static final class TreeBin<K,V> extends Node<K,V> {
}
```
### ConcurrentHashMap 线程安全的具体实现方式/底层具体实现
### ⭐️ConcurrentHashMap 线程安全的具体实现方式/底层具体实现
#### JDK1.8 之前
@ -520,7 +520,7 @@ Java 8 几乎完全重写了 `ConcurrentHashMap`,代码量从原来 Java 7 中
Java 8 中,锁粒度更细,`synchronized` 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,就不会影响其他 Node 的读写,效率大幅提升。
### JDK 1.7 和 JDK 1.8 的 ConcurrentHashMap 实现有什么不同?
### ⭐️JDK 1.7 和 JDK 1.8 的 ConcurrentHashMap 实现有什么不同?
- **线程安全实现方式**JDK 1.7 采用 `Segment` 分段锁来保证安全, `Segment` 是继承自 `ReentrantLock`。JDK1.8 放弃了 `Segment` 分段锁的设计,采用 `Node + CAS + synchronized` 保证线程安全,锁粒度更细,`synchronized` 只锁定当前链表或红黑二叉树的首节点。
- **Hash 碰撞解决方法** : JDK 1.7 采用拉链法JDK1.8 采用拉链法结合红黑树(链表长度超过一定阈值时,将链表转换为红黑树)。
@ -557,7 +557,7 @@ public static final Object NULL = new Object();
翻译过来之后的,大致意思还是单线程下可以容忍歧义,而多线程下无法容忍。
### ConcurrentHashMap 能保证复合操作的原子性吗?
### ⭐️ConcurrentHashMap 能保证复合操作的原子性吗?
`ConcurrentHashMap` 是线程安全的,意味着它可以保证多个线程同时对它进行读写操作时,不会出现数据不一致的情况,也不会导致 JDK1.7 及之前版本的 `HashMap` 多线程操作导致死循环问题。但是,这并不意味着它可以保证所有的复合操作都是原子性的,一定不要搞混了!

View File

@ -92,7 +92,7 @@ JDK 1.2 之前Java 线程是基于绿色线程Green Threads实现的
从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的**堆**和**方法区 (JDK1.8 之后的元空间)**资源,但是每个线程有自己的**程序计数器**、**虚拟机栈** 和 **本地方法栈**
**总结:** **线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。**
**总结:** 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。
下面是该知识点的扩展内容!
@ -143,7 +143,7 @@ Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种
线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。
Java 线程状态变迁图(图源:[挑错 |《Java 并发编程的艺术》中关于线程状态的三处错误](https://mp.weixin.qq.com/s/UOrXql_LhOD8dhTq_EPI0w))
Java 线程状态变迁图(图源:[挑错 |《Java 并发编程的艺术》中关于线程状态的三处错误](https://mp.weixin.qq.com/s/0UTyrJpRKaKhkhHcQtXAiA))
![Java 线程状态变迁图](https://oss.javaguide.cn/github/javaguide/java/concurrent/640.png)

View File

@ -130,7 +130,7 @@ public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
**AOP(Aspect-Oriented Programming面向切面编程)** 能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
**Spring AOP 就是基于动态代理的**,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 **JDK Proxy** 去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 **Cglib** 生成一个被代理对象的子类来作为代理,如下图所示:
Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 **JDK Proxy**去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 **Cglib** 生成一个被代理对象的子类来作为代理,如下图所示:
![SpringAOPProcess](https://oss.javaguide.cn/github/javaguide/SpringAOPProcess.jpg)