init: 初始化提交--源码学习笔记

This commit is contained in:
io2020 2025-07-21 01:06:10 +08:00
commit 018831037a
924 changed files with 164246 additions and 0 deletions

View File

View File

View File

27
Dubbo/file-create.py Normal file
View File

@ -0,0 +1,27 @@
import os
import pyperclip
# 获取当前脚本的路径
script_dir = os.path.dirname(os.path.abspath(__file__))
# 从剪贴板获取文件名列表,假设每行一个文件名
file_names = pyperclip.paste().strip().split('\n')
# 遍历文件名列表,为每个文件名添加排序前缀并创建文件
for index, file_name in enumerate(file_names, start=1):
# 去除文件名两端的空白字符
file_name = file_name.strip()
# 添加排序前缀和.md后缀
file_name_with_prefix = f"{index:02d}-{file_name}.md" # 这里使用3位数的排序前缀可以根据需要调整
# 构建文件的完整路径
file_path = os.path.join(script_dir, file_name_with_prefix)
# 创建文件
with open(file_path, 'w') as file:
# 如果需要写入初始内容,可以在这里添加
# 例如file.write("This is the content of the file.")
pass # 如果不需要写入内容则使用pass
print(f"Files created successfully in {script_dir}")

74
Dubbo/tmp.txt Normal file
View File

@ -0,0 +1,74 @@
Dubbo 面试题
Dubbo 学习指南
Dubbo 源码分析-调试环境搭建
Dubbo 源码分析-项目结构一览
Dubbo 源码分析-API 配置(一)之应用
Dubbo 源码分析-API 配置(二)之服务提供者
Dubbo 源码分析-API 配置(三)之服务消费者
Dubbo 源码分析-属性配置
Dubbo 源码分析-XML 配置
Dubbo 源码分析-注解配置
Dubbo 源码分析-外部化配置
Dubbo 源码分析-核心流程一览
Dubbo 源码分析-拓展机制 SPI
Dubbo 源码分析-线程池
Dubbo 源码分析-服务暴露之本地暴露Injvm
Dubbo 源码分析-服务暴露之远程暴露Dubbo
Dubbo 源码分析-服务引用之本地引用Injvm
Dubbo 源码分析-服务引用之远程引用Dubbo
Dubbo 源码分析-Zookeeper 客户端
Dubbo 源码分析-注册中心(一)之抽象 API
Dubbo 源码分析-注册中心(二)之 Zookeeper
Dubbo 源码分析-注册中心(三)之 Redis
Dubbo 源码分析-动态编译(一)之 Javassist
Dubbo 源码分析-动态代理(一)之 Javassist
Dubbo 源码分析-动态代理(二)之 JDK
Dubbo 源码分析-动态代理(三)之本地存根 Stub
Dubbo 源码分析-服务调用之本地调用Injvm
Dubbo 源码分析-服务调用之远程调用Dubbo【1】通信实现
Dubbo 源码分析-服务调用之远程调用Dubbo【2】同步调用
Dubbo 源码分析-服务调用之远程调用Dubbo【3】异步调用
Dubbo 源码分析-服务调用之远程调用HTTP
Dubbo 源码分析-服务调用之远程调用Hessian
Dubbo 源码分析-服务调用之远程调用WebService
Dubbo 源码分析-服务调用之远程调用REST
Dubbo 源码分析-服务调用之远程调用WebService
Dubbo 源码分析-服务调用之远程调用Redis
Dubbo 源码分析-服务调用之远程调用Memcached
Dubbo 源码分析-调用特性(一)之回声测试
Dubbo 源码分析-调用特性(二)之泛化引用
Dubbo 源码分析-调用特性(三)之泛化实现
Dubbo 源码分析-过滤器(一)之 ClassLoaderFilter
Dubbo 源码分析-过滤器(二)之 ContextFilter
Dubbo 源码分析-过滤器(三)之 AccessLogFilter
Dubbo 源码分析-过滤器(四)之 ActiveLimitFilter && ExecuteLimitFilter
Dubbo 源码分析-过滤器(五)之 TimeoutFilter
Dubbo 源码分析-过滤器(六)之 DeprecatedFilter
Dubbo 源码分析-过滤器(七)之 ExceptionFilter
Dubbo 源码分析-过滤器(八)之 TokenFilter
Dubbo 源码分析-过滤器(九)之 TpsLimitFilter
Dubbo 源码分析-过滤器(十)之 CacheFilter
Dubbo 源码分析-过滤器(十一)之 ValidationFilter
Dubbo 源码分析-NIO 服务器(一)之抽象 API
Dubbo 源码分析-NIO 服务器(二)之 Transport 层
Dubbo 源码分析-NIO 服务器(三)之 Telnet 层
Dubbo 源码分析-NIO 服务器(四)之 Exchange 层
Dubbo 源码分析-NIO 服务器(五)之 Buffer 层
Dubbo 源码分析-NIO 服务器(六)之 Netty4 实现
Dubbo 源码分析-NIO 服务器(七)之 Netty3 实现
Dubbo 源码分析-HTTP 服务器
Dubbo 源码分析-序列化(一)之总体实现
Dubbo 源码分析-序列化(二)之 Dubbo 实现
Dubbo 源码分析-序列化(三)之 Kryo 实现
Dubbo 源码分析-服务容器
Dubbo 源码解析-集群容错(一)之抽象 API
Dubbo 源码解析-集群容错(二)之 Cluster 实现
Dubbo 源码解析-集群容错(三)之 Directory 实现
Dubbo 源码解析-集群容错(四)之 LoadBalance 实现
Dubbo 源码解析-集群容错(五)之 Merger 实现
Dubbo 源码解析-集群容错(六)之 Configurator 实现
Dubbo 源码解析-集群容错(七)之 Router 实现
Dubbo 源码解析-集群容错(八)之 Mock 实现
Dubbo 源码解析-优雅停机
Dubbo 源码解析-日志适配
Dubbo 源码分析-集成 Spring Cloud

View File

@ -0,0 +1,65 @@
# 精尽 JDK 源码解析 —— 调试环境搭建(一)入门
> 本文,我们在原来文章 [《JDK 源码解析 —— 调试环境搭建》](http://vip.iocoder.cn/JDK/build-debugging-environment/?self) 的基础上,从 JDK11 调整成 JDK13 ,并使用 Git 取代 Mercurial 获取 OpenJDK 的源码。
>
> 在艿艿写完 [《精尽 JDK 源码解析 —— 调试环境搭建(二)进阶》](http://svip.iocoder.cn/JDK/build-debugging-environment-more?self) 之后,艿艿突然发现,从 Git 克隆的是 OpenJDK14 的 EAEarly Access抢 先体验版)。暂时不就纠正,反正胖友要记得,整个系列是基于 **OpenJDK14 的 EA** 写的。
# 0. 友情提示
JDK 源码的调试环境,实际上暂时没有特别好的方案。
因为,我们程序运行以来 JDK ,但是我们如果在 JDK 上增加源码相关的注释,就会导致代码行数的错乱。所以,艿艿目前的想法是,如下两个步骤:
- 1、从官方的 Mercurial 获取 OpenJDK 源码,在上面添加源码注释。
> 获取 OpenJDK 源码的方式,一共有三种
>
> - 1、从 http://jdk.java.net/ 网站上,直接下载源码。
> - 2、使用 Mercurial ,从 http://hg.openjdk.java.net/jdk/ 克隆对应版本的源码。例如说,我们在 [《JDK 源码解析 —— 调试环境搭建》](http://vip.iocoder.cn/JDK/build-debugging-environment/?self) 文章中,就是这么干的。
> - 3、使用 Git ,从 https://github.com/openjdk/jdk克隆对应版本的源码。该仓库就是 http://hg.openjdk.java.net/jdk/ 的镜像,方便我们使用我们所熟悉的 Git 而不是 Mercurial 。在本文,我们就会从该仓库克隆。
- 2、自己搭建一个项目使用 JDK13 ,然后进行调试。
# 1. 获取 OpenJDK 源码
从官方仓库 https://github.com/openjdk/jdk Fork 出属于自己的仓库。为什么要 Fork ?既然开始阅读、调试源码,我们可能会写一些注释,有了自己的仓库,可以进行自由的提交。😈
使用 IntelliJ IDEA 从 Fork 出来的仓库拉取代码。因为 JDK 的源码比较大,所以拉取会花费漫长时间,耐心等待下。
> 如果拉取比较慢,也可以考虑使用 https://gitee.com/zhijiantianya/jdk 地址。这个是艿艿使用 Gitee 对 https://github.com/openjdk/jdk 做的镜像。毕竟Github 对国内的网络,不是非常友好。
拉取完成后我们可以搜索想看的类。例如说HashMap 如下图所示:[![HashMap](http://static.iocoder.cn/images/JDK/2019_01_01/01.jpg)](http://static.iocoder.cn/images/JDK/2019_01_01/01.jpg)HashMap
如此,我们就可以愉快的在其上添加源码注释。
> 可能胖友导入在 IDEA 看到的项目结构比较奇怪。可以先 IDEA 关闭 OpenJDK 项目,然后删除项目本地根目录下面 IDEA 相关的配置,最后再使用 IDEA 打开 OpenJDK 项目。
# 2. 搭建项目调试
① 下载 JDK
从 https://www.oracle.com/technetwork/java/javase/downloads/jdk13-downloads-5672538.html 上,下载 Oracle JDK13 。
虽然说,我们在 [「1. 获取 OpenJDK 源码」](https://svip.iocoder.cn/JDK/build-debugging-environment/#) 中下载的是 OpenJDK 的源码,但是我们使用 Oracle JDK13 作为调试环境也是没啥问题的。因为Oracle JDK 和 OpenJDK 绝大部分源码是相同的。
> 如果想要纯粹以 OpenJDK 作为调试环境,可以参考 [《Mac 上的 JDK 多版本管理》](https://www.jishuwen.com/d/pm5V/zh-hk) 文章,使用 brew 安装对应版本的 OpenJDK 。
② 搭建项目
这个比较简单,就不重复赘述了。**需要注意的是,设置 Project SDK 要是 Oracle JDK13 噢**。
还有一个注意点IDEA 需要升级到 2019.2 或以上版本,因为 2019.2 才支持 JDK13 。
> 友情提示:当然,胖友如果自己有精力,可以将**克隆的** OpenJDK13 **编译**出来,作为项目使用的 Project SDK 。
③ 调试
直接调试,这个无需多说列。
例如说,艿艿就在 https://github.com/YunaiV/openjdk 项目下,创建了一个叫 `yunai` 的 Maven 项目,各种创建测试类,各种调试。
# 666. 彩蛋
2019 年的下半年,艿艿准备写写大家比较常用的 JDK 类的源码解析希望对胖友有一些帮助。共勉2019 ,加油!
当然,可能会有胖友会有疑惑,目前 JDK8 版本才是主流,而艿艿为什么使用 JDK13 呢虽然说Oracle JDK8 某个版本具体不太记得了之后需要更新但是我们最终还是会不断更新版本。Java 的强大,不是因为 Oracle ,而是完善的整个社区体系。

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

View File

@ -0,0 +1,200 @@
# 精尽 JDK 源码解析 —— 调试环境搭建(二)进阶
# 0. 概述
在 [《精尽 JDK 源码解析 —— 调试环境搭建(一)入门》](http://svip.iocoder.cn/JDK/build-debugging-environment?self) 中,我们已经能够简单的调试。但是,有些跟我一样“杠”的胖友,我要调试 JDK 源码,还要使用自己**编译**出来的 OpenJDK 。
对,所以本文,**我们就来自己编译 OpenJDK13 ,并使用它来调试**。
另外,因为每个人编译 OpenJDK 碰到的问题都不同,所以艿艿尽量多贴一些艿艿自己碰到的每个问题时,参考的文章。
还有一个,本文是基于 MacOS 系统。嘿嘿,因为手头暂时没有 Windows 的开发环境。
# 1. 依赖工具
## 1.1 编译源码
在 [《精尽 JDK 源码解析 —— 调试环境搭建(一)入门》](http://svip.iocoder.cn/JDK/build-debugging-environment?self) 中,我们已经使用 Git 从 https://github.com/YunaiV/openjdk 克隆了 OpenJDK**13** 的源码,直接使用它。
## 1.2 Xcode
编译需要用到 Xcode 工具一般情况下我们都已经安装的。如果没有安装的胖友记得安装下。T T 艿艿之前不小心卸载了,一脸懵逼。
建议的话,打开 https://apps.apple.com/cn/app/xcode/id497799835?mt=12 地址,去 App Store 更新到最新版本。
## 1.3 引导 JDK
> 艿艿:如果嫌弃哔哔的有点长,可以看本小节最后一端。
编译 OpenJDK 时,需要一个引导 JDKBoot JDK。具体原因艿艿看了下 [《Ubuntu 下编译 openjdk8》](https://www.jianshu.com/p/8a53708dd08a) 文章:
> 安装引导 jdk openjdk 的源码编译是需要引导 jdk 的,引导 jdk 需要比要编译的 openjdk 低一个版本,比如我们要编译 openjdk8 ,就需要 jdk7 来做引导 jdk ,可以是 openjdk7 ,也可以是 oraclejdk7 。
>
> 那么为什么需要引导 jdk 呢?我们知道 jdk 版本都是在前一个版本的基础上开发出来的,我们要编译的 openjdk8 在开发的时候,是需要用 jdk7 做环境的,在 jdk7 上做开发测试,是在 jdk7 的基础上开发出来的。
但是艿艿自己在编译 OpenJDK13 本地时,并未安装 JDK12 在本地。在 configure 成功,看了下自动使用了本地的 Oracle JDK13 (`java version "13-ea" 2019-09-17 Java(TM) SE Runtime Environment (build 13-ea+33) Java HotSpot(TM) 64-Bit Server VM (build 13-ea+33, mixed mode, sharing) (at /Library/Java/JavaVirtualMachines/jdk-13.jdk/Contents/Home)`) 。所以艿艿就搜了下,在 [《编译 OPENJDK 小记》](https://juejin.im/entry/5b3cbfaee51d451914629415) 文章:
> 还有一个比较坑的地方。以前编译过虚拟机的同学应该都知道,编译虚拟机需要一个 `boot jdk` 即为引导jdk。这个 `boot jdk` 是如果要编译 1.8 版本的jdk需要 1.7 版本的jdk。
>
> 但是笔者编译 `openjdk1.9` 的时候,总是报错 `boot jdk` 版本不对非常蛋疼笔者机器上装的1.8)。一开始我以为是路径不对,改了几次发现,其实编译会自动扫描 `JAVA_HOME` 下的jdk版本并且找一个合适的在此提醒各位如果发现有类似的问题赶紧按照提示用 `brew cask install java` 下载一个最新版本的 jdk 就好了。
所以呢,**引导 JDK 这步的安装,胖友可以先忽略**。在出现问题的时候,可以直接使用 `brew cask install java` 安装一个**最新版本**的 JDK ,也是可以引导成功的。
## 1.4 autoconf
```
brew install autoconf
```
如果不安装 autoconf 的话,待会在 make 时会找不到配置文件。
# 2. 开始编译
## 2.1 configure
在 OpenJDK 根目录下,执行命令如下:
```
# 这一句,仅仅是为了告诉胖友,一定要在根目录下
$ pwd
/Users/yunai/Java/openjdk13
# 如下命令,才是正觉
$ bash configure --enable-debug --with-jvm-variants=server --enable-dtrace
```
接着等待一会,如果出现如下日志,说明 configure 成功了:
```
Configuration summary:
* Debug level: fastdebug
* HS debug level: fastdebug
* JVM variants: server
* JVM features: server: 'aot cds cmsgc compiler1 compiler2 dtrace epsilongc g1gc graal jfr jni-check jvmci jvmti management nmt parallelgc serialgc services shenandoahgc vm-structs'
* OpenJDK target: OS: macosx, CPU architecture: x86, address length: 64
* Version string: 14-internal+0-adhoc.yunai.openjdk13 (14-internal)
Tools summary:
* Boot JDK: java version "13-ea" 2019-09-17 Java(TM) SE Runtime Environment (build 13-ea+33) Java HotSpot(TM) 64-Bit Server VM (build 13-ea+33, mixed mode, sharing) (at /Library/Java/JavaVirtualMachines/jdk-13.jdk/Contents/Home)
* Toolchain: clang (clang/LLVM from Xcode 11.0)
* C Compiler: Version 11.0.0 (at /usr/bin/clang)
* C++ Compiler: Version 11.0.0 (at /usr/bin/clang++)
Build performance summary:
* Cores to use: 12
* Memory limit: 32768 MB
```
不过往往,都是会碰到一些报错。艿艿在如下列举了部分,胖友逐个对照排查。如果没有,可以在星球里留言提问。
**报错 1**
报错如下:
```
configure: error: No xcodebuild tool and no system framework headers found, use --with-sysroot or --with-sdk-name to provide a path to a valid SDK
```
> 参考文章 [《如何在 macOS 中编译 OpenJDK10 源代码》](https://blog.csdn.net/asahinokawa/article/details/82226093) 。
输入如下命令:
```
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
```
**报错 2**
报错如下:
```
configure: error: Unable to determine SYSROOT and no headers found in /System/Library/Frameworks. Check Xcode configuration, --with-sysroot or --with-sdk-name arguments.
```
> 参考文章 [《build fails with OSX Mojave》](https://github.com/neovim/neovim/issues/9050) 。
输入如下命令:
```
open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg
```
## 2.2 make
在 OpenJDK 根目录下,执行命令如下:
```
$ export LANG=C
$ make all
```
接着好久一会,如果出现如下日志,说明 make 成功了:
```
Creating jdk image
Creating CDS archive for jdk image
Stopping sjavac server
Finished building target 'all' in configuration 'macosx-x86_64-server-fastdebug'
```
`build` 目录下,我们来看看我们编译的 `java` 的版本:
```
$ build/macosx-x86_64-server-fastdebug/jdk/
$ bin/java -version
openjdk version "14-internal" 2020-03-17
OpenJDK Runtime Environment (fastdebug build 14-internal+0-adhoc.yunai.openjdk13)
OpenJDK 64-Bit Server VM (fastdebug build 14-internal+0-adhoc.yunai.openjdk13, mixed mode)
```
- **T T 有一点很尴尬,实际我们使用的是 OpenJDK14 的 EAEarly Access抢 先体验版)。**
一般情况下,这步是不太会碰到报错,但是也可能会碰到一些报错。艿艿在如下列举了部分,胖友逐个对照排查。如果没有,可以在星球里留言提问。
**报错 1**
报错如下:
```
Undefined symbols for architecture x86_64:
"_objc_loadClassref", referenced from:
__ARCLite__load() in libarclite_macosx.a(arclite.o)
ld: symbol(s) not found for architecture x86_64
```
> 参考文章 https://bugs.openjdk.java.net/browse/JDK-8231572
删除 `Lib-java.base.gmk` 文件中的 `-fobjc-link-runtime` 代码。可以看看艿艿 Git 的[461da7026034fdd63df8a9776d0b11895f361523](https://github.com/YunaiV/openjdk/commit/461da7026034fdd63df8a9776d0b11895f361523) 提交。
# 3. 使用调试
**SDKs**
在 IDEA 中,创建我们编译出来的 OpenJDK 的 SDKs ,如下图所示:[![SDKs](02-JDK 源码解析-调试环境搭建(二)进阶.assets/01.png)](http://static.iocoder.cn/images/JDK/2019_01_04/01.png)SDKs
**Project SDK**
然后,记得将 Project SDK 修改成我们编译出来的 OpenJDK 的 SDK ,如下图所示:
[![Project SDK](02-JDK 源码解析-调试环境搭建(二)进阶.assets/02.png)](http://static.iocoder.cn/images/JDK/2019_01_04/02.png)Project SDK
**Module SDK**
最后,还要将 Module SDK 修改成我们编译出来的 OpenJDK 的 SDK ,如下图所示:[![Module SDK](02-JDK 源码解析-调试环境搭建(二)进阶.assets/03.png)](http://static.iocoder.cn/images/JDK/2019_01_04/03.png)Module SDK
之后,我们就可以愉快的调试了。
# 666. 彩蛋
编译的过程中,可能每个人碰到的问题不同,如下是艿艿在编译过程中,参考过的文章:
- [《Ubuntu 下编译 openjdk8》](https://www.jianshu.com/p/8a53708dd08a)
- [《Ubuntu 下编译 openjdk11》](https://www.jianshu.com/p/53472697f2ef)
- [《在 MAC 上编译 JDK》](https://www.jianshu.com/p/b8177780c939)
- [《JVM(一):java 技术体系与编译 openjdk》](https://www.jianshu.com/p/8523d36d1d6e)
- [《编译 openJdk10》](https://thurstonzk2008.com/2019/08/26/编译openjdk10/)
虽然 2013 年的时候,在 Ubuntu 上成功编译过一次 OpenJDK 。但是时隔 6 年之后,整个过程还是磕磕碰碰的。
国庆的最后一天假期的 21 点 55 分,准备休息,放空自己。

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,740 @@
# 精尽 JDK 源码解析 —— 集合(四)哈希表 LinkedHashMap
# 1. 概述
众所周知HashMap 提供的访问,是**无序**的。而在一些业务场景下,我们希望能够提供**有序**访问的 HashMap 。那么此时,我们就有两种选择:
- TreeMap :按照 key 的顺序。
- LinkedHashMap :按照 key 的插入和访问的顺序。
LinkedHashMap ,在 HashMap 的基础之上,提供了**顺序**访问的特性。而这里的顺序,包括两种:
- 按照 key-value 的**插入**顺序进行访问。关于这一点,相信大多数人都知道。
> 艿艿:如果不知道,那就赶紧知道。这不找抽么,哈哈哈。
- 按照 key-value 的**访问**顺序进行访问。通过这个特性,我们实现基于 LRU 算法的缓存。😈 相信这一点,可能还是有部分胖友不知道的噢,下文我们也会提供一个示例。
> 艿艿:面试中,有些面试官会喜欢问你,如何实现一个 LRU 的缓存。
实际上LinkedHashMap 可以理解成是 LinkedList + HashMap 的组合。为什么这么说呢?让我们带着这样的疑问,一起往下看。
# 2. 类图
LinkedHashMap 实现的接口、继承的类,如下图所示:[![类图](06-JDK 源码解析-集合(四)哈希表 LinkedHashMap.assets/01.png)](http://static.iocoder.cn/images/JDK/2019_12_10/01.png)类图
- 实现 Map 接口。
- 继承 HashMap 类。
😈 很简单,很粗暴。嘿嘿~
> 艿艿:因为 LinkedHashMap 继承自 HashMap 类,所以它的代码并不多,不到 500 行。
# 3. 属性
在开始看 LinkedHashMap 的属性之前,我们先来看在 [《精尽 JDK 源码解析 —— 集合(三)哈希表 HashMap》](http://svip.iocoder.cn/JDK/Collection-HashMap/) 看到的 HashMap 的 Node 子类图:[![Node 类图](06-JDK 源码解析-集合(四)哈希表 LinkedHashMap.assets/04.jpg)](http://static.iocoder.cn/images/JDK/2019_12_07/04.jpg)Node 类图
在图中,我们可以看到 LinkedHashMap 实现了自定义的节点 Entry ,一个支持指向前后节点的 Node 子类。代码如下:
```
// LinkedHashMap.java
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, // 前一个节点
after; // 后一个节点
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
```
- `before` 属性,指向前一个节点。`after` 属性,指向后一个节点。
- 通过 `before` + `after` 属性,我们就可以形成一个以 Entry 为节点的链表。😈 胖友,发挥下你的想象力。
既然 LinkedHashMap 是 LinkedList + HashMap 的组合,那么必然就会有头尾节点两兄弟。所以属性如下:
```
// LinkedHashMap.java
/**
* 头节点。
*
* 越老的节点,放在越前面。所以头节点,指向链表的开头
*
* The head (eldest) of the doubly linked list.
*/
transient LinkedHashMap.Entry<K,V> head;
/**
* 尾节点
*
* 越新的节点,放在越后面。所以尾节点,指向链表的结尾
*
* The tail (youngest) of the doubly linked list.
*/
transient LinkedHashMap.Entry<K,V> tail;
/**
* 是否按照访问的顺序。
*
* true :按照 key-value 的访问顺序进行访问。
* false :按照 key-value 的插入顺序进行访问。
*
* The iteration ordering method for this linked hash map: {@code true}
* for access-order, {@code false} for insertion-order.
*
* @serial
*/
final boolean accessOrder;
```
- 仔细看下每个属性的注释。
- `head` + `tail` 属性,形成 LinkedHashMap 的双向链表。而访问的顺序,就是 `head => tail` 的过程。
- ```
accessOrder
```
属性,决定了 LinkedHashMap 的顺序。也就是说:
- `true` 时,当 Entry 节点被访问时,放置到链表的结尾,被 `tail` 指向。
- `false` 时,当 Entry 节点被添加时,放置到链表的结尾,被 `tail` 指向。如果插入的 key 对应的 Entry 节点已经存在,也会被放到结尾。
总结来说,就是如下一张图:
> FROM [《Working of LinkedHashMap》](http://www.thejavageek.com/2016/06/05/working-linkedhashmap-java/)
>
> [![LinkedHashMap 结构图](http://www.thejavageek.com/wp-content/uploads/2016/06/SecondObjectInserted.png)](http://www.thejavageek.com/wp-content/uploads/2016/06/SecondObjectInserted.png)LinkedHashMap 结构图
# 4. 构造方法
LinkedHashMap 一共有 5 个构造方法,其中四个和 HashMap 相同,只是多初始化 `accessOrder = false` 。所以,默认使用**插入**顺序进行访问。
另外一个 `#LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)` 构造方法,允许自定义 `accessOrder` 属性。代码如下:
```
// LinkedHashMap.java
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
```
# 5. 创建节点
在插入 key-value 键值对时,例如说 `#put(K key, V value)` 方法,如果不存在对应的节点,则会调用 `#newNode(int hash, K key, V value, Node<K,V> e)` 方法,创建节点。
因为 LinkedHashMap 自定义了 Entry 节点,所以必然需要重写该方法。代码如下:
```
// LinkedHashMap.java
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
// <1> 创建 Entry 节点
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<>(hash, key, value, e);
// <2> 添加到结尾
linkNodeLast(p);
// 返回
return p;
}
```
- `<1>` 处,创建 Entry 节点。虽然此处传入 `e` 作为 `Entry.next` 属性,指向下一个节点。但是实际上,`#put(K key, V value)` 方法中,传入的 `e = null`
- `<2>` 处,调用 `#linkNodeLast(LinkedHashMap.Entry<K,V> p)` 方法,添加到结尾。代码如下:
```
// LinkedHashMap.java
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
// 记录原尾节点到 last 中
LinkedHashMap.Entry<K,V> last = tail;
// 设置 tail 指向 p ,变更新的尾节点
tail = p;
// 如果原尾节点 last 为空,说明 head 也为空,所以 head 也指向 p
if (last == null)
head = p;
// last <=> p ,相互指向
else {
p.before = last;
last.after = p;
}
}
```
- 这样,符合越新的节点,放到链表的越后面。
# 6. 节点操作回调
在 HashMap 的读取、添加、删除时,分别提供了 `#afterNodeAccess(Node<K,V> e)``#afterNodeInsertion(boolean evict)``#afterNodeRemoval(Node<K,V> e)` 回调方法。这样LinkedHashMap 可以通过它们实现自定义拓展逻辑。
## 6.1 afterNodeAccess
`accessOrder` 属性为 `true` 时,当 Entry 节点被访问时,放置到链表的结尾,被 `tail` 指向。所以 `#afterNodeAccess(Node<K,V> e)` 方法的代码如下:
```
// LinkedHashMap.java
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
// accessOrder 判断必须是满足按访问顺序。
// (last = tail) != e 将 tail 赋值给 last ,并且判断是否 e 已经是队尾。如果是队尾,就不用处理了。
if (accessOrder && (last = tail) != e) {
// 将 e 赋值给 p 【因为要 Node 类型转换成 Entry 类型】
// 同时 b、a 分别是 e 的前后节点
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
// 第一步,将 p 从链表中移除
p.after = null;
// 处理 b 的下一个节点指向 a
if (b == null)
head = a;
else
b.after = a;
// 处理 a 的前一个节点指向 b
if (a != null)
a.before = b;
else
last = b;
// 第二步,将 p 添加到链表的尾巴。实际这里的代码,和 linkNodeLast 是一致的。
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
// tail 指向 p ,实际就是 e 。
tail = p;
// 增加修改次数
++modCount;
}
}
```
- 代码已经添加详细的注释,胖友认真看看噢。
- 链表的操作看起来比较繁琐实际一共分成两步1第一步`p` 从链表中移除2`p` 添加到链表的尾巴。
因为 HashMap 提供的 `#get(Object key)``#getOrDefault(Object key, V defaultValue)` 方法,并未调用 `#afterNodeAccess(Node<K,V> e)` 方法,这在按照**读取**顺序访问显然不行,所以 LinkedHashMap 重写这两方法的代码,如下:
```
// LinkedHashMap.java
public V get(Object key) {
// 获得 key 对应的 Node
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
// 如果访问到,回调节点被访问
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
public V getOrDefault(Object key, V defaultValue) {
// 获得 key 对应的 Node
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return defaultValue;
// 如果访问到,回调节点被访问
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
```
## 6.2 afterNodeInsertion
在开始看 `#afterNodeInsertion(boolean evict)` 方法之前,我们先来看看如何基于 LinkedHashMap 实现 LRU 算法的缓存。代码如下:
```
class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int CACHE_SIZE;
/**
* 传递进来最多能缓存多少数据
*
* @param cacheSize 缓存大小
*/
public LRUCache(int cacheSize) {
// true 表示让 LinkedHashMap 按照访问顺序来进行排序,最近访问的放在头部,最老访问的放在尾部。
super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
CACHE_SIZE = cacheSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
// 当 map 中的数据量大于指定的缓存个数的时候,就自动删除最老的数据。
return size() > CACHE_SIZE;
}
}
```
为什么能够这么实现呢?我们在 `#afterNodeInsertion(boolean evict)` 方法中来理解。代码如下:
```
// LinkedHashMap.java
// evict 翻译为驱逐,表示是否允许移除元素
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
// first = head 记录当前头节点。因为移除从头开始,最老
// <1> removeEldestEntry(first) 判断是否满足移除最老节点
if (evict && (first = head) != null && removeEldestEntry(first)) {
// <2> 移除指定节点
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
```
- `<1>` 处,调用 `#removeEldestEntry(Map.Entry<K,V> eldest)` 方法,判断是否移除最老节点。代码如下:
```
// LinkedHashMap.java
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
```
- 默认情况下,**都不**移除最老的节点。所以在上述的 LRU 缓存的示例,重写了该方法,判断 LinkedHashMap 是否超过缓存最大大小。如果是,则移除最老的节点。
- `<2>` 处,如果满足条件,则调用 `#removeNode(...)` 方法,移除最老的节点。
😈 这样,是不是很容易理解基于 LinkedHashMap 实现 LRU 算法的缓存。
## 6.3 afterNodeRemoval
在节点被移除时LinkedHashMap 需要将节点也从链表中移除,所以重写 `#afterNodeRemoval(Node<K,V> e)` 方法,实现该逻辑。代码如下:
```
// LinkedHashMap.java
void afterNodeRemoval(Node<K,V> e) { // unlink
// 将 e 赋值给 p 【因为要 Node 类型转换成 Entry 类型】
// 同时 b、a 分别是 e 的前后节点
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
// 将 p 从链表中移除
p.before = p.after = null;
// 处理 b 的下一个节点指向 a
if (b == null)
head = a;
else
b.after = a;
// 处理 a 的前一个节点指向 b
if (a == null)
tail = b;
else
a.before = b;
}
```
# 7. 转换成数组
因为 LinkedHashMap 需要满足按**顺序**访问,所以需要重写 HashMap 提供的好多方法,例如说本小节我们看到的几个。
`#keysToArray(T[] a)` 方法,转换出 key 数组**顺序**返回。代码如下:
```
// LinkedHashMap.java
@Override
final <T> T[] keysToArray(T[] a) {
Object[] r = a;
int idx = 0;
// 通过 head 顺序遍历,从头到尾
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {
r[idx++] = e.key;
}
return a;
}
```
- 要小心噢,必须保证 `a` 放得下 LinkedHashMap 所有的元素。
`#valuesToArray(T[] a)` 方法,转换出 value 数组**顺序**返回。代码如下:
```
// LinkedHashMap.java
@Override
final <T> T[] valuesToArray(T[] a) {
Object[] r = a;
int idx = 0;
// 通过 head 顺序遍历,从头到尾
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {
r[idx++] = e.value;
}
return a;
}
```
> 艿艿:看到此处,胖友基本可以结束本文落。
# 8. 转换成 Set/Collection
`#keySet()` 方法,获得 key Set 。代码如下:
```
// LinkedHashMap.java
public Set<K> keySet() {
// 获得 keySet 缓存
Set<K> ks = keySet;
// 如果不存在,则进行创建
if (ks == null) {
ks = new LinkedKeySet(); // LinkedKeySet 是 LinkedHashMap 自定义的
keySet = ks;
}
return ks;
}
```
- 其中, LinkedKeySet 是 LinkedHashMap 自定义的 Set 实现类。代码如下:
```
// LinkedHashMap.java
final class LinkedKeySet extends AbstractSet<K> {
public final int size() { return size; }
public final void clear() { LinkedHashMap.this.clear(); }
public final Iterator<K> iterator() {
return new LinkedKeyIterator(); // <X>
}
public final boolean contains(Object o) { return containsKey(o); }
public final boolean remove(Object key) {
return removeNode(hash(key), key, null, false, true) != null;
}
public final Spliterator<K> spliterator() {
return Spliterators.spliterator(this, Spliterator.SIZED |
Spliterator.ORDERED |
Spliterator.DISTINCT);
}
public Object[] toArray() {
return keysToArray(new Object[size]);
}
public <T> T[] toArray(T[] a) {
return keysToArray(prepareArray(a));
}
public final void forEach(Consumer<? super K> action) {
if (action == null)
throw new NullPointerException();
int mc = modCount;
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
action.accept(e.key);
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
```
- 其内部,调用的都是 LinkedHashMap 提供的方法。
`#values()` 方法,获得 value Collection 。代码如下:
```
// LinkedHashMap.java
public Collection<V> values() {
// 获得 values 缓存
Collection<V> vs = values;
// 如果不存在,则进行创建
if (vs == null) {
vs = new LinkedValues(); // LinkedValues 是 LinkedHashMap 自定义的
values = vs;
}
return vs;
}
```
- 其中, LinkedValues 是 LinkedHashMap 自定义的 Collection 实现类。代码如下:
```
// LinkedHashMap.java
final class LinkedValues extends AbstractCollection<V> {
public final int size() { return size; }
public final void clear() { LinkedHashMap.this.clear(); }
public final Iterator<V> iterator() {
return new LinkedValueIterator(); // <X>
}
public final boolean contains(Object o) { return containsValue(o); }
public final Spliterator<V> spliterator() {
return Spliterators.spliterator(this, Spliterator.SIZED |
Spliterator.ORDERED);
}
public Object[] toArray() {
return valuesToArray(new Object[size]);
}
public <T> T[] toArray(T[] a) {
return valuesToArray(prepareArray(a));
}
public final void forEach(Consumer<? super V> action) {
if (action == null)
throw new NullPointerException();
int mc = modCount;
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
action.accept(e.value);
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
```
- 其内部,调用的都是 LinkedHashMap 提供的方法。
`#entrySet()` 方法,获得 key-value Set 。代码如下:
```
// LinkedHashMap.java
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
// LinkedEntrySet 是 LinkedHashMap 自定义的
return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
}
```
- 其中, LinkedEntrySet 是 LinkedHashMap 自定义的 Set 实现类。代码如下:
```
// LinkedHashMap.java
final class LinkedEntrySet extends AbstractSet<Map.Entry<K,V>> {
public final int size() { return size; }
public final void clear() { LinkedHashMap.this.clear(); }
public final Iterator<Map.Entry<K,V>> iterator() {
return new LinkedEntryIterator(); // <X>
}
public final boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Node<K,V> candidate = getNode(hash(key), key);
return candidate != null && candidate.equals(e);
}
public final boolean remove(Object o) {
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Object value = e.getValue();
return removeNode(hash(key), key, value, true, true) != null;
}
return false;
}
public final Spliterator<Map.Entry<K,V>> spliterator() {
return Spliterators.spliterator(this, Spliterator.SIZED |
Spliterator.ORDERED |
Spliterator.DISTINCT);
}
public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
if (action == null)
throw new NullPointerException();
int mc = modCount;
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
action.accept(e);
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
```
- 其内部,调用的都是 LinkedHashMap 提供的方法。
在上面的代码中,艿艿实际标记了三处 `<X>` 标记,分别是 LinkedKeyIterator、LinkedValueIterator、LinkedEntryIterator ,用于迭代遍历 key、value、Entry 。而它们都继承了 LinkedHashIterator 抽象类,代码如下:
```
// LinkedHashMap.java
abstract class LinkedHashIterator {
/**
* 下一个节点
*/
LinkedHashMap.Entry<K,V> next;
/**
* 当前节点
*/
LinkedHashMap.Entry<K,V> current;
/**
* 修改次数
*/
int expectedModCount;
LinkedHashIterator() {
next = head;
expectedModCount = modCount;
current = null;
}
public final boolean hasNext() {
return next != null;
}
final LinkedHashMap.Entry<K,V> nextNode() {
LinkedHashMap.Entry<K,V> e = next;
// 如果发生了修改,抛出 ConcurrentModificationException 异常
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
// 如果 e 为空,说明没有下一个节点,则抛出 NoSuchElementException 异常
if (e == null)
throw new NoSuchElementException();
// 遍历到下一个节点
current = e;
next = e.after;
return e;
}
public final void remove() {
// 移除当前节点
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
// 如果发生了修改,抛出 ConcurrentModificationException 异常
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
// 标记 current 为空,因为被移除了
current = null;
// 移除节点
removeNode(p.hash, p.key, null, false, false);
// 修改 expectedModCount 次数
expectedModCount = modCount;
}
}
final class LinkedKeyIterator extends LinkedHashIterator
implements Iterator<K> {
// key
public final K next() { return nextNode().getKey(); }
}
final class LinkedValueIterator extends LinkedHashIterator
implements Iterator<V> {
// value
public final V next() { return nextNode().value; }
}
final class LinkedEntryIterator extends LinkedHashIterator
implements Iterator<Map.Entry<K,V>> {
// Entry
public final Map.Entry<K,V> next() { return nextNode(); }
}
```
# 9. 清空
`#clear()` 方法,清空 LinkedHashMap 。代码如下:
```
// LinkedHashMap.java
public void clear() {
// 清空
super.clear();
// 标记 head 和 tail 为 null
head = tail = null;
}
```
- 需要额外清空 `head``tail`
# 10. 其它方法
> 本小节,我们会罗列下其他 LinkedHashMap 重写的方法。当然,可以选择不看。
在序列化时,会调用到 `#internalWriteEntries(java.io.ObjectOutputStream s)` 方法,重写代码如下:
```
// LinkedHashMap.java
void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException {
// 通过 head 顺序遍历,从头到尾
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {
// 写入 key
s.writeObject(e.key);
// 写入 value
s.writeObject(e.value);
}
}
```
在反序列化时,会调用 `#reinitialize()` 方法,重写代码如下:
```
// LinkedHashMap.java
void reinitialize() {
// 调用父方法,初始化
super.reinitialize();
// 标记 head 和 tail 为 null
head = tail = null;
}
```
查找值时,会调用 `#containsValue(Object value)` 方法,重写代码如下:
```
// LinkedHashMap.java
public boolean containsValue(Object value) {
// 通过 head 顺序遍历,从头到尾
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {
V v = e.value;
// 判断是否相等
if (v == value || (value != null && value.equals(v)))
return true;
}
return false;
}
```
# 666. 彩蛋
如下几个方法,是 LinkedHashMap 重写和红黑树相关的几个方法,胖友可以自己瞅瞅:
- `#replacementNode(Node<K,V> p, Node<K,V> next)`
- `#newTreeNode(int hash, K key, V value, Node<K,V> next)`
- `#replacementTreeNode(Node<K,V> p, Node<K,V> next)`
- `#transferLinks(LinkedHashMap.Entry<K,V> src, LinkedHashMap.Entry<K,V> dst)`
下面,我们来对 LinkedHashMap 做一个简单的小结:
- LinkedHashMap 是 HashMap 的子类,增加了
顺序
访问的特性。
- 【默认】当 `accessOrder = false` 时,按照 key-value 的**插入**顺序进行访问。
- 当 `accessOrder = true` 时,按照 key-value 的**读取**顺序进行访问。
- LinkedHashMap 的**顺序**特性,通过内部的双向链表实现,所以我们把它看成是 LinkedList + LinkedHashMap 的组合。
- LinkedHashMap 通过重写 HashMap 提供的回调方法,从而实现其对**顺序**的特性的处理。同时,因为 LinkedHashMap 的**顺序**特性,需要重写 `#keysToArray(T[] a)` 等**遍历**相关的方法。
- LinkedHashMap 可以方便实现 LRU 算法的缓存,

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,301 @@
# 精尽 JDK 源码解析 —— 集合(五)哈希集合 HashSet
# 1. 概述
> 艿艿:突然有点词穷,不知道怎么介绍,嘿嘿。
HashSet ,基于 HashMap 的 Set 实现类。在业务中,如果我们有排重的需求,一般会考虑使用 HashSet 。
在 Redis 提供的 Set 数据结构,不考虑编码的情况下,它是基于 Redis 自身的 Hash 数据结构实现的。这点JDK 和 Redis 是相同的。😈 感兴趣的胖友,看完本文后,可以自己去撸一撸。
# 2. 类图
HashSet 实现的接口、继承的类,如下图所示:[![类图](07-JDK 源码解析-集合(五)哈希集合 HashSet.assets/01.png)](http://static.iocoder.cn/images/JDK/2019_12_13/01.png)类图
- 实现 [`java.util.Set`](https://github.com/YunaiV/openjdk/blob/master/src/java.base/share/classes/java/util/Set.java) 接口,并继承 [`java.util.AbstractSet`](https://github.com/YunaiV/openjdk/blob/master/src/java.base/share/classes/java/util/AbstractSet.java) 抽像类。
- 实现 [`java.io.Serializable`](https://github.com/YunaiV/openjdk/blob/master/src/java.base/share/classes/java/io/Serializable.java) 接口。
- 实现 [`java.lang.Cloneable`](https://github.com/YunaiV/openjdk/blob/master/src/java.base/share/classes/java/lang/Cloneable.java) 接口。
# 3. 属性
HashSet 只有一个属性,那就是 `map` 。代码如下:
```
// HashSet.java
private transient HashMap<E, Object> map;
```
- `map` 的 key ,存储 HashSet 的每个 key 。
- `map` 的 value ,因为 HashSet 没有 value 的需要,所以使用一个统一的 `PRESENT` 即可。代码如下:
```
// HashSet.java
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
```
# 4. 构造方法
HashSet 一共有 5 个构造方法,代码如下:
```
// HashSet.java
public HashSet() {
map = new HashMap<>();
}
public HashSet(Collection<? extends E> c) {
// 最小必须是 16 。
// (int) (c.size()/.75f) + 1 避免扩容
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
// 批量添加
addAll(c);
}
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor); // 注意,这种情况下的构造方法,创建的是 LinkedHashMap 对象
}
```
- 在构造方法中,会创建 HashMap 或 LinkedHashMap 对象。
# 5. 添加单个元素
`#add(E e)` 方法,添加单个元素。代码如下:
```
// HashSet.java
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
```
- `map` 的 value 值,就是我们看到的 `PRESENT`
而添加多个元素,继承自 AbstractCollection 抽象类,通过 `#addAll(Collection<? extends E> c)` 方法,代码如下:
```
// AbstractCollection.java
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
// 遍历 c 集合,逐个添加
for (E e : c)
if (add(e))
modified = true;
return modified;
}
```
- 在方法内部,会逐个调用 `#add(E e)` 方法,逐个添加单个元素。
# 6. 移除单个元素
`#remove(Object key)` 方法,移除 key 对应的 value ,并返回该 value 。代码如下:
```
// HashSet.java
public boolean remove(Object o) {
return map.remove(o) == PRESENT;
}
```
# 7. 查找单个元素
`#contains(Object key)` 方法,判断 key 是否存在。代码如下:
```
// HashSet.java
public boolean contains(Object o) {
return map.containsKey(o);
}
```
> 艿艿:后面的内容,快速看即可。
# 8. 转换成数组
`#toArray(T[] a)` 方法,转换出 key 数组返回。代码如下:
```
// HashSet.java
@Override
public Object[] toArray() {
return map.keysToArray(new Object[map.size()]);
}
@Override
public <T> T[] toArray(T[] a) {
return map.keysToArray(map.prepareArray(a));
}
```
# 9. 清空
`#clear()` 方法,清空 HashSet 。代码如下:
```
// HashSet.java
public void clear() {
map.clear();
}
```
# 10. 序列化
`#writeObject(ObjectOutputStream s)` 方法,序列化 HashSet 对象。代码如下:
```
// HashSet.java
@java.io.Serial
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
// 写入非静态属性、非 transient 属性
s.defaultWriteObject();
// Write out HashMap capacity and load factor
// 写入 map table 数组大小
s.writeInt(map.capacity());
// 写入 map 加载因子
s.writeFloat(map.loadFactor());
// Write out size
// 写入 map 大小
s.writeInt(map.size());
// Write out all elements in the proper order.
// 遍历 map ,逐个 key 序列化
for (E e : map.keySet())
s.writeObject(e);
}
```
# 11. 反序列化
`#readObject(ObjectInputStream s)` 方法,反序列化成 HashSet 对象。代码如下:
```
// HashSet.java
@java.io.Serial
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
// 读取非静态属性、非 transient 属性
s.defaultReadObject();
// Read capacity and verify non-negative.
// 读取 HashMap table 数组大小
int capacity = s.readInt();
// 校验 capacity 参数
if (capacity < 0) {
throw new InvalidObjectException("Illegal capacity: " +
capacity);
}
// Read load factor and verify positive and non NaN.
// 获得加载因子 loadFactor
float loadFactor = s.readFloat();
// 校验 loadFactor 参数
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
}
// Read size and verify non-negative.
// 读取 key-value 键值对数量 size
int size = s.readInt();
// 校验 size 参数
if (size < 0) {
throw new InvalidObjectException("Illegal size: " +
size);
}
// Set the capacity according to the size and load factor ensuring that
// the HashMap is at least 25% full but clamping to maximum capacity.
// 计算容量
capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
HashMap.MAXIMUM_CAPACITY);
// Constructing the backing map will lazily create an array when the first element is
// added, so check it before construction. Call HashMap.tableSizeFor to compute the
// actual allocation size. Check Map.Entry[].class since it's the nearest public type to
// what is actually created.
SharedSecrets.getJavaObjectInputStreamAccess()
.checkArray(s, Map.Entry[].class, HashMap.tableSizeFor(capacity)); // 不知道作甚,哈哈哈。
// Create backing HashMap
// 创建 LinkedHashMap 或 HashMap 对象
map = (((HashSet<?>)this) instanceof LinkedHashSet ?
new LinkedHashMap<>(capacity, loadFactor) :
new HashMap<>(capacity, loadFactor));
// Read in all elements in the proper order.
// 遍历读取 key 键,添加到 map 中
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}
```
# 12. 克隆
`#clone()` 方法,克隆 HashSet 对象。代码如下:
```
// HashSet.java
public Object clone() {
try {
// 调用父方法,克隆创建 newSet 对象
HashSet<E> newSet = (HashSet<E>) super.clone();
// 可控 mao 属性,赋值给 newSet
newSet.map = (HashMap<E, Object>) map.clone();
// 返回
return newSet;
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
```
# 13. 获得迭代器
`#iterator()` 方法,获得迭代器。代码如下:
```
// HashSet.java
public Iterator<E> iterator() {
return map.keySet().iterator();
}
```
# 666. 彩蛋
😈 总的来说,比较简单,相信胖友一会会时间就已经看完了。
可能偶尔我们会使用到 LinkedHashSet ,它是 HashSet 的子类,胖友自己去看。更加简单~
关于 HashSet 的总结只有一句话HashSet 是基于 HashMap 的 Set 实现类。

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Some files were not shown because too many files have changed in this diff Show More