init: 初始化提交--源码学习笔记
0
Dubbo/01-Dubbo 面试题.md
Normal file
0
Dubbo/02-Dubbo 学习指南.md
Normal file
0
Dubbo/03-Dubbo 源码分析-调试环境搭建.md
Normal file
0
Dubbo/04-Dubbo 源码分析-项目结构一览.md
Normal file
0
Dubbo/05-Dubbo 源码分析-API 配置(一)之应用.md
Normal file
0
Dubbo/06-Dubbo 源码分析-API 配置(二)之服务提供者.md
Normal file
0
Dubbo/07-Dubbo 源码分析-API 配置(三)之服务消费者.md
Normal file
0
Dubbo/08-Dubbo 源码分析-属性配置.md
Normal file
0
Dubbo/09-Dubbo 源码分析-XML 配置.md
Normal file
0
Dubbo/10-Dubbo 源码分析-注解配置.md
Normal file
0
Dubbo/11-Dubbo 源码分析-外部化配置.md
Normal file
0
Dubbo/12-Dubbo 源码分析-核心流程一览.md
Normal file
0
Dubbo/13-Dubbo 源码分析-拓展机制 SPI.md
Normal file
0
Dubbo/14-Dubbo 源码分析-线程池.md
Normal file
0
Dubbo/15-Dubbo 源码分析-服务暴露(一)之本地暴露(Injvm).md
Normal file
0
Dubbo/16-Dubbo 源码分析-服务暴露(二)之远程暴露(Dubbo).md
Normal file
0
Dubbo/17-Dubbo 源码分析-服务引用(一)之本地引用(Injvm).md
Normal file
0
Dubbo/18-Dubbo 源码分析-服务引用(二)之远程引用(Dubbo).md
Normal file
0
Dubbo/19-Dubbo 源码分析-Zookeeper 客户端.md
Normal file
0
Dubbo/20-Dubbo 源码分析-注册中心(一)之抽象 API.md
Normal file
0
Dubbo/21-Dubbo 源码分析-注册中心(二)之 Zookeeper.md
Normal file
0
Dubbo/22-Dubbo 源码分析-注册中心(三)之 Redis.md
Normal file
0
Dubbo/23-Dubbo 源码分析-动态编译(一)之 Javassist.md
Normal file
0
Dubbo/24-Dubbo 源码分析-动态代理(一)之 Javassist.md
Normal file
0
Dubbo/25-Dubbo 源码分析-动态代理(二)之 JDK.md
Normal file
0
Dubbo/26-Dubbo 源码分析-动态代理(三)之本地存根 Stub.md
Normal file
0
Dubbo/27-Dubbo 源码分析-服务调用(一)之本地调用(Injvm).md
Normal file
0
Dubbo/28-Dubbo 源码分析-服务调用(二)之远程调用(Dubbo)【1】通信实现.md
Normal file
0
Dubbo/29-Dubbo 源码分析-服务调用(二)之远程调用(Dubbo)【2】同步调用.md
Normal file
0
Dubbo/30-Dubbo 源码分析-服务调用(三)之远程调用(Dubbo)【3】异步调用.md
Normal file
0
Dubbo/31-Dubbo 源码分析-服务调用(三)之远程调用(HTTP).md
Normal file
0
Dubbo/32-Dubbo 源码分析-服务调用(四)之远程调用(Hessian).md
Normal file
0
Dubbo/33-Dubbo 源码分析-服务调用(五)之远程调用(WebService).md
Normal file
0
Dubbo/34-Dubbo 源码分析-服务调用(六)之远程调用(REST).md
Normal file
0
Dubbo/35-Dubbo 源码分析-服务调用(七)之远程调用(WebService).md
Normal file
0
Dubbo/36-Dubbo 源码分析-服务调用(八)之远程调用(Redis).md
Normal file
0
Dubbo/37-Dubbo 源码分析-服务调用(九)之远程调用(Memcached).md
Normal file
0
Dubbo/38-Dubbo 源码分析-调用特性(一)之回声测试.md
Normal file
0
Dubbo/39-Dubbo 源码分析-调用特性(二)之泛化引用.md
Normal file
0
Dubbo/40-Dubbo 源码分析-调用特性(三)之泛化实现.md
Normal file
0
Dubbo/41-Dubbo 源码分析-过滤器(一)之 ClassLoaderFilter.md
Normal file
0
Dubbo/42-Dubbo 源码分析-过滤器(二)之 ContextFilter.md
Normal file
0
Dubbo/43-Dubbo 源码分析-过滤器(三)之 AccessLogFilter.md
Normal file
0
Dubbo/45-Dubbo 源码分析-过滤器(五)之 TimeoutFilter.md
Normal file
0
Dubbo/46-Dubbo 源码分析-过滤器(六)之 DeprecatedFilter.md
Normal file
0
Dubbo/47-Dubbo 源码分析-过滤器(七)之 ExceptionFilter.md
Normal file
0
Dubbo/48-Dubbo 源码分析-过滤器(八)之 TokenFilter.md
Normal file
0
Dubbo/49-Dubbo 源码分析-过滤器(九)之 TpsLimitFilter.md
Normal file
0
Dubbo/50-Dubbo 源码分析-过滤器(十)之 CacheFilter.md
Normal file
0
Dubbo/51-Dubbo 源码分析-过滤器(十一)之 ValidationFilter.md
Normal file
0
Dubbo/52-Dubbo 源码分析-NIO 服务器(一)之抽象 API.md
Normal file
0
Dubbo/53-Dubbo 源码分析-NIO 服务器(二)之 Transport 层.md
Normal file
0
Dubbo/54-Dubbo 源码分析-NIO 服务器(三)之 Telnet 层.md
Normal file
0
Dubbo/55-Dubbo 源码分析-NIO 服务器(四)之 Exchange 层.md
Normal file
0
Dubbo/56-Dubbo 源码分析-NIO 服务器(五)之 Buffer 层.md
Normal file
0
Dubbo/57-Dubbo 源码分析-NIO 服务器(六)之 Netty4 实现.md
Normal file
0
Dubbo/58-Dubbo 源码分析-NIO 服务器(七)之 Netty3 实现.md
Normal file
0
Dubbo/59-Dubbo 源码分析-HTTP 服务器.md
Normal file
0
Dubbo/60-Dubbo 源码分析-序列化(一)之总体实现.md
Normal file
0
Dubbo/61-Dubbo 源码分析-序列化(二)之 Dubbo 实现.md
Normal file
0
Dubbo/62-Dubbo 源码分析-序列化(三)之 Kryo 实现.md
Normal file
0
Dubbo/63-Dubbo 源码分析-服务容器.md
Normal file
0
Dubbo/64-Dubbo 源码解析-集群容错(一)之抽象 API.md
Normal file
0
Dubbo/65-Dubbo 源码解析-集群容错(二)之 Cluster 实现.md
Normal file
0
Dubbo/66-Dubbo 源码解析-集群容错(三)之 Directory 实现.md
Normal file
0
Dubbo/67-Dubbo 源码解析-集群容错(四)之 LoadBalance 实现.md
Normal file
0
Dubbo/68-Dubbo 源码解析-集群容错(五)之 Merger 实现.md
Normal file
0
Dubbo/69-Dubbo 源码解析-集群容错(六)之 Configurator 实现.md
Normal file
0
Dubbo/70-Dubbo 源码解析-集群容错(七)之 Router 实现.md
Normal file
0
Dubbo/71-Dubbo 源码解析-集群容错(八)之 Mock 实现.md
Normal file
0
Dubbo/72-Dubbo 源码解析-优雅停机.md
Normal file
0
Dubbo/73-Dubbo 源码解析-日志适配.md
Normal file
0
Dubbo/74-Dubbo 源码分析-集成 Spring Cloud.md
Normal file
27
Dubbo/file-create.py
Normal 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
@ -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
|
65
jdk/01-JDK 源码解析-调试环境搭建(一)入门.md
Normal 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 的 EA(Early 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 如下图所示:[](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 ,而是完善的整个社区体系。
|
BIN
jdk/02-JDK 源码解析-调试环境搭建(二)进阶.assets/01.png
Normal file
After Width: | Height: | Size: 532 KiB |
BIN
jdk/02-JDK 源码解析-调试环境搭建(二)进阶.assets/02.png
Normal file
After Width: | Height: | Size: 173 KiB |
BIN
jdk/02-JDK 源码解析-调试环境搭建(二)进阶.assets/03.png
Normal file
After Width: | Height: | Size: 116 KiB |
200
jdk/02-JDK 源码解析-调试环境搭建(二)进阶.md
Normal 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 时,需要一个引导 JDK(Boot 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 的 EA(Early 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 ,如下图所示:[进阶.assets/01.png)](http://static.iocoder.cn/images/JDK/2019_01_04/01.png)SDKs
|
||||||
|
|
||||||
|
**Project SDK**
|
||||||
|
|
||||||
|
然后,记得将 Project SDK 修改成我们编译出来的 OpenJDK 的 SDK ,如下图所示:
|
||||||
|
|
||||||
|
[进阶.assets/02.png)](http://static.iocoder.cn/images/JDK/2019_01_04/02.png)Project SDK
|
||||||
|
|
||||||
|
**Module SDK**
|
||||||
|
|
||||||
|
最后,还要将 Module SDK 修改成我们编译出来的 OpenJDK 的 SDK ,如下图所示:[进阶.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 分,准备休息,放空自己。
|
BIN
jdk/03-JDK 源码解析-集合(一)数组 ArrayList.assets/01.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
jdk/03-JDK 源码解析-集合(一)数组 ArrayList.assets/02.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
jdk/03-JDK 源码解析-集合(一)数组 ArrayList.assets/03.png
Normal file
After Width: | Height: | Size: 146 KiB |
BIN
jdk/03-JDK 源码解析-集合(一)数组 ArrayList.assets/04.png
Normal file
After Width: | Height: | Size: 96 KiB |
1471
jdk/03-JDK 源码解析-集合(一)数组 ArrayList.md
Normal file
BIN
jdk/04-JDK 源码解析-集合(二)链表 LinkedList.assets/01.jpg
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
jdk/04-JDK 源码解析-集合(二)链表 LinkedList.assets/02.jpg
Normal file
After Width: | Height: | Size: 40 KiB |
1279
jdk/04-JDK 源码解析-集合(二)链表 LinkedList.md
Normal file
BIN
jdk/05-JDK 源码解析-集合(三)哈希表 HashMap.assets/01.jpg
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
jdk/05-JDK 源码解析-集合(三)哈希表 HashMap.assets/02.jpg
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
jdk/05-JDK 源码解析-集合(三)哈希表 HashMap.assets/03.jpg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
jdk/05-JDK 源码解析-集合(三)哈希表 HashMap.assets/04.jpg
Normal file
After Width: | Height: | Size: 22 KiB |
1430
jdk/05-JDK 源码解析-集合(三)哈希表 HashMap.md
Normal file
BIN
jdk/06-JDK 源码解析-集合(四)哈希表 LinkedHashMap.assets/01.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
jdk/06-JDK 源码解析-集合(四)哈希表 LinkedHashMap.assets/04.jpg
Normal file
After Width: | Height: | Size: 22 KiB |
740
jdk/06-JDK 源码解析-集合(四)哈希表 LinkedHashMap.md
Normal 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 实现的接口、继承的类,如下图所示:[哈希表 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 子类图:[哈希表 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/)
|
||||||
|
>
|
||||||
|
> [](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 算法的缓存,
|
BIN
jdk/07-JDK 源码解析-集合(五)哈希集合 HashSet.assets/01.png
Normal file
After Width: | Height: | Size: 11 KiB |
301
jdk/07-JDK 源码解析-集合(五)哈希集合 HashSet.md
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
# 精尽 JDK 源码解析 —— 集合(五)哈希集合 HashSet
|
||||||
|
|
||||||
|
# 1. 概述
|
||||||
|
|
||||||
|
> 艿艿:突然有点词穷,不知道怎么介绍,嘿嘿。
|
||||||
|
|
||||||
|
HashSet ,基于 HashMap 的 Set 实现类。在业务中,如果我们有排重的需求,一般会考虑使用 HashSet 。
|
||||||
|
|
||||||
|
在 Redis 提供的 Set 数据结构,不考虑编码的情况下,它是基于 Redis 自身的 Hash 数据结构实现的。这点,JDK 和 Redis 是相同的。😈 感兴趣的胖友,看完本文后,可以自己去撸一撸。
|
||||||
|
|
||||||
|
# 2. 类图
|
||||||
|
|
||||||
|
HashSet 实现的接口、继承的类,如下图所示:[哈希集合 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 实现类。
|
BIN
jdk/08-JDK 源码解析-集合(六)TreeMap.assets/01.png
Normal file
After Width: | Height: | Size: 16 KiB |