diff --git a/README.md b/README.md
index 8ca82576..0f3ef86f 100644
--- a/README.md
+++ b/README.md
@@ -21,12 +21,12 @@
> [剑指 Offer 题解](notes/剑指%20offer%20题解.md)
-目录根据原书第二版进行编排,代码和原书有所不同,尽量比原书更简洁。
+ 目录根据原书第二版进行编排,代码和原书有所不同,尽量比原书更简洁。
> [Leetcode 题解](notes/Leetcode%20题解.md)
-对题目做了一个大致分类,并对每种题型的解题思路做了总结。
-
+ 对题目做了一个大致分类,并对每种题型的解题思路做了总结。
+
> [算法](notes/算法.md)
排序、并查集、栈和队列、红黑树、散列表。
@@ -35,141 +35,141 @@
> [计算机操作系统](notes/计算机操作系统.md)
-进程管理、内存管理、设备管理、链接。
+ 进程管理、内存管理、设备管理、链接。
> [Linux](notes/Linux.md)
-基本实现原理以及基本操作。
+ 基本实现原理以及基本操作。
## 网络 :cloud:
> [计算机网络](notes/计算机网络.md)
-物理层、链路层、网络层、运输层、应用层。
+ 物理层、链路层、网络层、运输层、应用层。
> [HTTP](notes/HTTP.md)
-方法、状态码、Cookie、缓存、连接管理、HTTPs、HTTP 2.0。
+ 方法、状态码、Cookie、缓存、连接管理、HTTPs、HTTP 2.0。
> [Socket](notes/Socket.md)
-I/O 模型、I/O 多路复用。
+ I/O 模型、I/O 多路复用。
## 面向对象 :couple:
> [设计模式](notes/设计模式.md)
-实现了 Gof 的 23 种设计模式。
+ 实现了 Gof 的 23 种设计模式。
> [面向对象思想](notes/面向对象思想.md)
-三大原则(继承、封装、多态)、类图、设计原则。
+ 三大原则(继承、封装、多态)、类图、设计原则。
## 数据库 :floppy_disk:
> [数据库系统原理](notes/数据库系统原理.md)
-事务、锁、隔离级别、MVCC、间隙锁、范式。
+ 事务、锁、隔离级别、MVCC、间隙锁、范式。
> [SQL](notes/SQL.md)
-SQL 基本语法。
+ SQL 基本语法。
> [Leetcode-Database 题解](notes/Leetcode-Database%20题解.md)
-Leetcode 上数据库题目的解题记录。
+ Leetcode 上数据库题目的解题记录。
> [MySQL](notes/MySQL.md)
-存储引擎、索引、查询优化、切分、复制。
+ 存储引擎、索引、查询优化、切分、复制。
> [Redis](notes/Redis.md)
-五种数据类型、字典和跳跃表数据结构、使用场景、和 Memcache 的比较、淘汰策略、持久化、文件事件的 Reactor 模式、复制。
+ 五种数据类型、字典和跳跃表数据结构、使用场景、和 Memcache 的比较、淘汰策略、持久化、文件事件的 Reactor 模式、复制。
## Java :coffee:
> [Java 基础](notes/Java%20基础.md)
-不会涉及很多基本语法介绍,主要是一些实现原理以及关键特性。
+ 不会涉及很多基本语法介绍,主要是一些实现原理以及关键特性。
> [Java 容器](/notes/Java%20容器.md)
-源码分析:ArrayList、Vector、CopyOnWriteArrayList、LinkedList、HashMap、ConcurrentHashMap、LinkedHashMap、WeekHashMap。
+ 源码分析:ArrayList、Vector、CopyOnWriteArrayList、LinkedList、HashMap、ConcurrentHashMap、LinkedHashMap、WeekHashMap。
> [Java 并发](notes/Java%20并发.md)
-线程使用方式、两种互斥同步方法、线程协作、JUC、线程安全、内存模型、锁优化。
+ 线程使用方式、两种互斥同步方法、线程协作、JUC、线程安全、内存模型、锁优化。
> [Java 虚拟机](notes/Java%20虚拟机.md)
-运行时数据区域、垃圾收集、类加载。
+ 运行时数据区域、垃圾收集、类加载。
> [Java I/O](notes/Java%20IO.md)
-NIO 的原理以及实例。
+ NIO 的原理以及实例。
## 系统设计 :bulb:
> [系统设计基础](notes/系统设计基础.md)
-性能、伸缩性、扩展性、可用性、安全性
+ 性能、伸缩性、扩展性、可用性、安全性
> [分布式](notes/分布式.md)
-分布式锁、分布式事务、CAP、BASE、Paxos、Raft
+ 分布式锁、分布式事务、CAP、BASE、Paxos、Raft
> [集群](notes/集群.md)
-负载均衡、Session 管理
+ 负载均衡、Session 管理
> [攻击技术](notes/攻击技术.md)
-XSS、CSRF、SQL 注入、DDoS
+ XSS、CSRF、SQL 注入、DDoS
> [缓存](notes/缓存.md)
-缓存特征、缓存位置、缓存问题、数据分布、一致性哈希、LRU、CDN
+ 缓存特征、缓存位置、缓存问题、数据分布、一致性哈希、LRU、CDN
> [消息队列](notes/消息队列.md)
-消息处理模型、使用场景、可靠性
+ 消息处理模型、使用场景、可靠性
## 工具 :hammer:
> [Git](notes/Git.md)
-一些 Git 的使用和概念。
+ 一些 Git 的使用和概念。
> [Docker](notes/Docker.md)
-Docker 基本原理。
+ Docker 基本原理。
> [正则表达式](notes/正则表达式.md)
-正则表达式基本语法。
+ 正则表达式基本语法。
> [构建工具](notes/构建工具.md)
-构建工具的基本概念、主流构建工具介绍。
+ 构建工具的基本概念、主流构建工具介绍。
## 编码实践 :speak_no_evil:
> [重构](notes/重构.md)
-参考 重构 改善既有代码的设计。
+ 参考 重构 改善既有代码的设计。
> [代码可读性](notes/代码可读性.md)
-参考 编写可读代码的艺术。
+ 参考 编写可读代码的艺术。
> [代码风格规范](notes/代码风格规范.md)
-Google 开源项目的代码风格规范。
+ Google 开源项目的代码风格规范。
## 后记 :memo:
-**About**
+### About
这个仓库是笔者的一个学习笔记,主要总结一些比较重要的知识点,希望对大家有所帮助。
@@ -177,7 +177,7 @@ Google 开源项目的代码风格规范。
[BOOKLIST](BOOKLIST.md),这个书单是笔者至今看的一些比较好的技术书籍,虽然没有全都看完,但每本书多多少少都看了一部分。
-**How To Contribute**
+### How To Contribute
笔记内容是笔者一个字一个字打上去的,难免会有一些笔误,如果发现笔误可直接在相应文档进行编辑修改。
@@ -185,13 +185,7 @@ Google 开源项目的代码风格规范。
欢迎在 Issue 中提交对本仓库的改进建议~
-**Authorization**
-
-虽然没有加开源协议,但是允许非商业性使用。
-
-转载使用请注明出处,谢谢!
-
-**Typesetting**
+### Typesetting
笔记内容按照 [中文文案排版指北](http://mazhuang.org/wiki/chinese-copywriting-guidelines/) 进行排版,以保证内容的可读性。
@@ -199,7 +193,7 @@ Google 开源项目的代码风格规范。
笔者将自己实现的文档排版功能提取出来,放在 Github Page 中,无需下载安装即可免费使用:[Text-Typesetting](https://github.com/CyC2018/Markdown-Typesetting)。
-**Uploading**
+### Uploading
笔者在本地使用为知笔记软件进行书写,为了方便将本地笔记内容上传到 Github 上,实现了一整套自动化上传方案,包括文本文件的导出、提取图片、Markdown 文档转换、Git 同步。
@@ -207,15 +201,21 @@ Google 开源项目的代码风格规范。
笔者将自己实现文档转换功能提取出来,方便大家在需要将本地 Markdown 上传到 Github,或者制作项目 README 文档时生成目录时使用:[GFM-Converter](https://github.com/CyC2018/GFM-Converter)。
-**Statement**
+### License
+
+在对本作品进行演绎时,请署名并以相同方式共享。
+
+
+
+### Statement
本仓库不参与商业行为,不向读者收取任何费用。(This repository is not engaging in business activities, and does not charge readers any fee.)
-**Logo**
+### Logo
Power by [logomakr](https://logomakr.com/).
-**Acknowledgements**
+### Acknowledgements
感谢以下人员对本仓库做出的贡献,当然不仅仅只有这些贡献者,这里就不一一列举了。如果你希望被添加到这个名单中,并且提交过 Issue 或者 PR,请与笔者联系。
diff --git a/notes/HTTP.md b/notes/HTTP.md
index 243dc283..537b24fe 100644
--- a/notes/HTTP.md
+++ b/notes/HTTP.md
@@ -45,7 +45,7 @@
* [二进制分帧层](#二进制分帧层)
* [服务端推送](#服务端推送)
* [首部压缩](#首部压缩)
-* [八、GET 和 POST 的区别](#八get-和-post-的区别)
+* [八、GET 和 POST 比较](#八get-和-post-比较)
* [作用](#作用)
* [参数](#参数)
* [安全](#安全)
@@ -61,13 +61,13 @@
## URL
-- URI(Uniform Resource Identifier,统一资源标识符)
-- URL(Uniform Resource Locator,统一资源定位符)
-- URN(Uniform Resource Name,统一资源名称),例如 urn:isbn:0-486-27557-4。
-
URI 包含 URL 和 URN,目前 WEB 只有 URL 比较流行,所以见到的基本都是 URL。
-
+- URI(Uniform Resource Identifier,统一资源标识符)
+- URL(Uniform Resource Locator,统一资源定位符)
+- URN(Uniform Resource Name,统一资源名称)
+
+
## 请求和响应报文
@@ -197,7 +197,7 @@ CONNECT www.example.com:443 HTTP/1.1
- **204 No Content** :请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。
-- **206 Partial Content** :表示客户端进行了范围请求。响应报文包含由 Content-Range 指定范围的实体内容。
+- **206 Partial Content** :表示客户端进行了范围请求,响应报文包含由 Content-Range 指定范围的实体内容。
## 3XX 重定向
@@ -219,7 +219,7 @@ CONNECT www.example.com:443 HTTP/1.1
- **401 Unauthorized** :该状态码表示发送的请求需要有认证信息(BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。
-- **403 Forbidden** :请求被拒绝,服务器端没有必要给出拒绝的详细理由。
+- **403 Forbidden** :请求被拒绝。
- **404 Not Found**
@@ -331,7 +331,7 @@ Set-Cookie: tasty_cookie=strawberry
[page content]
```
-客户端之后对同一个服务器发送请求时,会从浏览器中读出 Cookie 信息通过 Cookie 请求首部字段发送给服务器。
+客户端之后对同一个服务器发送请求时,会从浏览器中取出 Cookie 信息并通过 Cookie 请求首部字段发送给服务器。
```html
GET /sample_page.html HTTP/1.1
@@ -382,9 +382,9 @@ Path 标识指定了主机下的哪些路径可以接受 Cookie(该 URL 路径
除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。
-Session 可以存储在服务器上的文件、数据库或者内存中。也可以将 Session 存储在内存型数据库中,比如 Redis,效率会更高。
+Session 可以存储在服务器上的文件、数据库或者内存中。也可以将 Session 存储在 Redis 这种内存型数据库中,效率会更高。
-使用 Session 维护用户登录的过程如下:
+使用 Session 维护用户登录状态的过程如下:
- 用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中;
- 服务器验证该用户名和密码;
@@ -499,7 +499,7 @@ If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
### 1. 短连接与长连接
-当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要断开一次 TCP 连接,连接建立和断开的开销会很大。
+当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要新建一个 TCP 连接,那么开销会很大。
长连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。
@@ -688,7 +688,7 @@ HTTPs 并不是新协议,而是让 HTTP 先和 SSL(Secure Sockets Layer)
### 3. HTTPs 采用的加密方式
-HTTPs 采用混合的加密机制,使用非对称密钥加密用于传输对称密钥来保证安全性,之后使用对称密钥加密进行通信来保证效率。(下图中的 Session Key 就是对称密钥)
+HTTPs 采用混合的加密机制,使用非对称密钥加密用于传输对称密钥来保证传输过程的安全性,之后使用对称密钥加密进行通信来保证通信过程的效率。(下图中的 Session Key 就是对称密钥)
@@ -717,7 +717,7 @@ HTTPs 的报文摘要功能之所以安全,是因为它结合了加密和认
## HTTPs 的缺点
- 因为需要进行加密解密等过程,因此速度会更慢;
-- 需要支付证书授权的高费用。
+- 需要支付证书授权的高额费用。
## 配置 HTTPs
@@ -727,7 +727,7 @@ HTTPs 的报文摘要功能之所以安全,是因为它结合了加密和认
## HTTP/1.x 缺陷
- HTTP/1.x 实现简单是以牺牲应用性能为代价的:
+ HTTP/1.x 实现简单是以牺牲性能为代价的:
- 客户端需要使用多个连接才能实现并发和缩短延迟;
- 不会压缩请求和响应首部,从而导致不必要的网络流量;
@@ -763,7 +763,7 @@ HTTP/2.0 要求客户端和服务器同时维护和更新一个包含之前见
-# 八、GET 和 POST 的区别
+# 八、GET 和 POST 比较
## 作用
@@ -870,6 +870,7 @@ DELETE /idX/delete HTTP/1.1 -> Returns 404
- [MDN : HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP)
- [HTTP/2 简介](https://developers.google.com/web/fundamentals/performance/http2/?hl=zh-cn)
- [htmlspecialchars](http://php.net/manual/zh/function.htmlspecialchars.php)
+- [Difference between file URI and URL in java](http://java2db.com/java-io/how-to-get-and-the-difference-between-file-uri-and-url-in-java)
- [How to Fix SQL Injection Using Java PreparedStatement & CallableStatement](https://software-security.sans.org/developer-how-to/fix-sql-injection-in-java-using-prepared-callable-statement)
- [浅谈 HTTP 中 Get 与 Post 的区别](https://www.cnblogs.com/hyddd/archive/2009/03/31/1426026.html)
- [Are http:// and www really necessary?](https://www.webdancers.com/are-http-and-www-necesary/)
diff --git a/notes/Java IO.md b/notes/Java IO.md
index 1e3aedcd..a14ea902 100644
--- a/notes/Java IO.md
+++ b/notes/Java IO.md
@@ -2,8 +2,17 @@
* [一、概览](#一概览)
* [二、磁盘操作](#二磁盘操作)
* [三、字节操作](#三字节操作)
+ * [实现文件复制](#实现文件复制)
+ * [装饰者模式](#装饰者模式)
* [四、字符操作](#四字符操作)
+ * [编码与解码](#编码与解码)
+ * [String](#string)
+ * [Reader 与 Writer](#reader-与-writer)
+ * [实现逐行输出文本文件的内容](#实现逐行输出文本文件的内容)
* [五、对象操作](#五对象操作)
+ * [序列化](#序列化)
+ * [Serializable](#serializable)
+ * [transient](#transient)
* [六、网络操作](#六网络操作)
* [InetAddress](#inetaddress)
* [URL](#url)
@@ -37,7 +46,7 @@ Java 的 I/O 大概可以分成以下几类:
File 类可以用于表示文件和目录的信息,但是它不表示文件的内容。
-递归地输出一个目录下所有文件:
+递归地列出一个目录下所有文件:
```java
public static void listAllFiles(File dir) {
@@ -56,7 +65,7 @@ public static void listAllFiles(File dir) {
# 三、字节操作
-使用字节流操作进行文件复制:
+## 实现文件复制
```java
public static void copyFile(String src, String dist) throws IOException {
@@ -77,13 +86,15 @@ public static void copyFile(String src, String dist) throws IOException {
}
```
-
+## 装饰者模式
Java I/O 使用了装饰者模式来实现。以 InputStream 为例,
- InputStream 是抽象组件;
- FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作;
-- FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能,例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。
+- FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能。例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。
+
+
实例化一个具有缓存功能的字节流对象时,只需要在 FileInputStream 对象上再套一层 BufferedInputStream 对象即可。
@@ -96,27 +107,7 @@ DataInputStream 装饰者提供了对更多数据类型进行输入的操作,
# 四、字符操作
-不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符。但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。
-
-- InputStreamReader 实现从字节流解码成字符流;
-- OutputStreamWriter 实现字符流编码成为字节流。
-
-逐行输出文本文件的内容:
-
-```java
-public static void readFileContent(String filePath) throws IOException {
- FileReader fileReader = new FileReader(filePath);
- BufferedReader bufferedReader = new BufferedReader(fileReader);
- String line;
- while ((line = bufferedReader.readLine()) != null) {
- System.out.println(line);
- }
- // 装饰者模式使得 BufferedReader 组合了一个 Reader 对象
- // 在调用 BufferedReader 的 close() 方法时会去调用 fileReader 的 close() 方法
- // 因此只要一个 close() 调用即可
- bufferedReader.close();
-}
-```
+## 编码与解码
编码就是把字符转换为字节,而解码是把字节重新组合成字符。
@@ -130,6 +121,8 @@ UTF-16be 中的 be 指的是 Big Endian,也就是大端。相应地也有 UTF-
Java 使用双字节编码 UTF-16be,这不是指 Java 只支持这一种编码方式,而是说 char 这种类型使用 UTF-16be 进行编码。char 类型占 16 位,也就是两个字节,Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char 来存储。
+## String
+
String 可以看成一个字符序列,可以指定一个编码方式将它编码为字节序列,也可以指定一个编码方式将一个字节序列解码为 String。
```java
@@ -145,13 +138,46 @@ System.out.println(str2);
byte[] bytes = str1.getBytes();
```
+## Reader 与 Writer
+
+不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符。但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。
+
+- InputStreamReader 实现从字节流解码成字符流;
+- OutputStreamWriter 实现字符流编码成为字节流。
+
+## 实现逐行输出文本文件的内容
+
+```java
+public static void readFileContent(String filePath) throws IOException {
+
+ FileReader fileReader = new FileReader(filePath);
+ BufferedReader bufferedReader = new BufferedReader(fileReader);
+
+ String line;
+ while ((line = bufferedReader.readLine()) != null) {
+ System.out.println(line);
+ }
+
+ // 装饰者模式使得 BufferedReader 组合了一个 Reader 对象
+ // 在调用 BufferedReader 的 close() 方法时会去调用 Reader 的 close() 方法
+ // 因此只要一个 close() 调用即可
+ bufferedReader.close();
+}
+```
+
# 五、对象操作
+## 序列化
+
序列化就是将一个对象转换成字节序列,方便存储和传输。
- 序列化:ObjectOutputStream.writeObject()
- 反序列化:ObjectInputStream.readObject()
+不会对静态变量进行序列化,因为序列化只是保存对象的状态,静态变量属于类的状态。
+
+## Serializable
+
序列化的类需要实现 Serializable 接口,它只是一个标准,没有任何方法需要实现,但是如果不去实现它的话而进行序列化,会抛出异常。
```java
@@ -184,11 +210,11 @@ private static class A implements Serializable {
}
```
-不会对静态变量进行序列化,因为序列化只是保存对象的状态,静态变量属于类的状态。
+## transient
transient 关键字可以使一些属性不会被序列化。
-ArrayList 中存储数据的数组是用 transient 修饰的,因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。
+ArrayList 中存储数据的数组 elementData 是用 transient 修饰的,因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。
```java
private transient Object[] elementData;
@@ -249,8 +275,8 @@ public static void main(String[] args) throws IOException {
## Datagram
-- DatagramPacket:数据包类
- DatagramSocket:通信类
+- DatagramPacket:数据包类
# 七、NIO
diff --git a/notes/Java 基础.md b/notes/Java 基础.md
index bcb94acc..44ca873a 100644
--- a/notes/Java 基础.md
+++ b/notes/Java 基础.md
@@ -62,7 +62,10 @@ int y = x; // 拆箱
## 缓存池
-new Integer(123) 与 Integer.valueOf(123) 的区别在于,new Integer(123) 每次都会新建一个对象,而 Integer.valueOf(123) 可能会使用缓存对象,因此多次使用 Integer.valueOf(123) 会取得同一个对象的引用。
+new Integer(123) 与 Integer.valueOf(123) 的区别在于:
+
+- new Integer(123) 每次都会新建一个对象
+- Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用。
```java
Integer x = new Integer(123);
@@ -73,14 +76,6 @@ Integer k = Integer.valueOf(123);
System.out.println(z == k); // true
```
-编译器会在自动装箱过程调用 valueOf() 方法,因此多个 Integer 实例使用自动装箱来创建并且值相同,那么就会引用相同的对象。
-
-```java
-Integer m = 123;
-Integer n = 123;
-System.out.println(m == n); // true
-```
-
valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。
```java
@@ -125,7 +120,15 @@ static {
}
```
-Java 还将一些其它基本类型的值放在缓冲池中,包含以下这些:
+编译器会在自动装箱过程调用 valueOf() 方法,因此多个 Integer 实例使用自动装箱来创建并且值相同,那么就会引用相同的对象。
+
+```java
+Integer m = 123;
+Integer n = 123;
+System.out.println(m == n); // true
+```
+
+基本类型对应的缓冲池如下:
- boolean values true and false
- all byte values
@@ -133,7 +136,7 @@ Java 还将一些其它基本类型的值放在缓冲池中,包含以下这些
- int values between -128 and 127
- char in the range \u0000 to \u007F
-因此在使用这些基本类型对应的包装类型时,就可以直接使用缓冲池中的对象。
+在使用这些基本类型对应的包装类型时,就可以直接使用缓冲池中的对象。
[StackOverflow : Differences between new Integer(123), Integer.valueOf(123) and just 123
](https://stackoverflow.com/questions/9030817/differences-between-new-integer123-integer-valueof123-and-just-123)
@@ -186,15 +189,15 @@ String 不可变性天生具备线程安全,可以在多个线程中安全地
- String 不可变,因此是线程安全的
- StringBuilder 不是线程安全的
-- StringBuffer 是线程安全的,内部使用 synchronized 来同步
+- StringBuffer 是线程安全的,内部使用 synchronized 进行同步
[StackOverflow : String, StringBuffer, and StringBuilder](https://stackoverflow.com/questions/2971315/string-stringbuffer-and-stringbuilder)
## String.intern()
-使用 String.intern() 可以保证相同内容的字符串变量引用相同的内存对象。
+使用 String.intern() 可以保证相同内容的字符串变量引用同一的内存对象。
-下面示例中,s1 和 s2 采用 new String() 的方式新建了两个不同对象,而 s3 是通过 s1.intern() 方法取得一个对象引用,这个方法首先把 s1 引用的对象放到 String Pool(字符串常量池)中,然后返回这个对象引用。因此 s3 和 s1 引用的是同一个字符串常量池的对象。
+下面示例中,s1 和 s2 采用 new String() 的方式新建了两个不同对象,而 s3 是通过 s1.intern() 方法取得一个对象引用。intern() 首先把 s1 引用的对象放到 String Pool(字符串常量池)中,然后返回这个对象引用。因此 s3 和 s1 引用的是同一个字符串常量池的对象。
```java
String s1 = new String("aaa");
@@ -214,7 +217,7 @@ System.out.println(s4 == s5); // true
在 Java 7 之前,字符串常量池被放在运行时常量池中,它属于永久代。而在 Java 7,字符串常量池被移到 Native Method 中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。
-- [StackOverflow : What is String interning?](https://stackoverflow.com/questions/10578984/what-is-string-interning)
+- [StackOverflow : What is String interning?](https://stackoverflow.com/questions/10578984/what-is-string-interning)
- [深入解析 String#intern](https://tech.meituan.com/in_depth_understanding_string_intern.html)
# 三、运算
@@ -223,7 +226,7 @@ System.out.println(s4 == s5); // true
Java 的参数是以值传递的形式传入方法中,而不是引用传递。
-以下代码中 Dog dog 的 dog 是一个指针,存储的是对象的地址。在将一个参数传入一个方法时,本质上是将对象的地址以值的方式传递到形参中。但是如果在方法中改变对象的字段值会改变原对象该字段值,因为改变的是同一个地址指向的内容。
+以下代码中 Dog dog 的 dog 是一个指针,存储的是对象的地址。在将一个参数传入一个方法时,本质上是将对象的地址以值的方式传递到形参中。因此在方法中改变指针引用的对象,那么这两个指针此时指向的是完全不同的对象,一方改变其所指向对象的内容对另一方没有影响。
```java
public class Dog {
@@ -234,7 +237,11 @@ public class Dog {
}
String getName() {
- return name;
+ return this.name;
+ }
+
+ void setName(String name) {
+ this.name = name;
}
String getObjectAddress() {
@@ -262,6 +269,22 @@ public class PassByValueExample {
}
```
+但是如果在方法中改变对象的字段值会改变原对象该字段值,因为改变的是同一个地址指向的内容。
+
+```java
+class PassByValueExample {
+ public static void main(String[] args) {
+ Dog dog = new Dog("A");
+ func(dog);
+ System.out.println(dog.getName()); // B
+ }
+
+ private static void func(Dog dog) {
+ dog.setName("B");
+ }
+}
+```
+
[StackOverflow: Is Java “pass-by-reference” or “pass-by-value”?](https://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value)
## float 与 double
@@ -317,7 +340,7 @@ switch (s) {
}
```
-switch 不支持 long,是因为 switch 的设计初衷是为那些只需要对少数的几个值进行等值判断,如果值过于复杂,那么还是用 if 比较合适。
+switch 不支持 long,是因为 switch 的设计初衷是对那些只有少数的几个值进行等值判断,如果值过于复杂,那么还是用 if 比较合适。
```java
// long x = 111;
@@ -341,33 +364,37 @@ Java 中有三个访问权限修饰符:private、protected 以及 public,如
可以对类或类中的成员(字段以及方法)加上访问修饰符。
-- 成员可见表示其它类可以用这个类的实例对象访问到该成员;
- 类可见表示其它类可以用这个类创建实例对象。
+- 成员可见表示其它类可以用这个类的实例对象访问到该成员;
protected 用于修饰成员,表示在继承体系中成员对于子类可见,但是这个访问修饰符对于类没有意义。
设计良好的模块会隐藏所有的实现细节,把它的 API 与它的实现清晰地隔离开来。模块之间只通过它们的 API 进行通信,一个模块不需要知道其他模块的内部工作情况,这个概念被称为信息隐藏或封装。因此访问权限应当尽可能地使每个类或者成员不被外界访问。
-如果子类的方法覆盖了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使用父类实例的地方都可以使用子类实例,也就是确保满足里氏替换原则。
+如果子类的方法重写了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使用父类实例的地方都可以使用子类实例,也就是确保满足里氏替换原则。
-字段决不能是公有的,因为这么做的话就失去了对这个字段修改行为的控制,客户端可以对其随意修改。可以使用公有的 getter 和 setter 方法来替换公有字段。
+字段决不能是公有的,因为这么做的话就失去了对这个字段修改行为的控制,客户端可以对其随意修改。例如下面的例子中,AccessExample 拥有 id 共有字段,如果在某个时刻,我们想要使用 int 去存储 id 字段,那么就需要去修改所有的客户端代码。
```java
public class AccessExample {
- public int x;
+ public String id;
}
```
+可以使用公有的 getter 和 setter 方法来替换公有字段,这样的话就可以控制对字段的修改行为。
+
+
```java
public class AccessExample {
- private int x;
- public int getX() {
- return x;
+ private int id;
+
+ public String getId() {
+ return id + "";
}
- public void setX(int x) {
- this.x = x;
+ public void setId(String id) {
+ this.id = Integer.valueOf(id);
}
}
```
@@ -387,7 +414,7 @@ public class AccessWithInnerClassExample {
}
public int getValue() {
- return innerClass.x; // 直接访问
+ return innerClass.x; // 直接访问
}
}
```
@@ -396,7 +423,7 @@ public class AccessWithInnerClassExample {
**1. 抽象类**
-抽象类和抽象方法都使用 abstract 进行声明。抽象类一般会包含抽象方法,抽象方法一定位于抽象类中。
+抽象类和抽象方法都使用 abstract 关键字进行声明。抽象类一般会包含抽象方法,抽象方法一定位于抽象类中。
抽象类和普通类最大的区别是,抽象类不能被实例化,需要继承抽象类才能实例化其子类。
@@ -415,7 +442,7 @@ public abstract class AbstractClassExample {
```
```java
-public class AbstractExtendClassExample extends AbstractClassExample{
+public class AbstractExtendClassExample extends AbstractClassExample {
@Override
public void func1() {
System.out.println("func1");
@@ -448,7 +475,7 @@ public interface InterfaceExample {
}
int x = 123;
- // int y; // Variable 'y' might not have been initialized
+ // int y; // Variable 'y' might not have been initialized
public int z = 0; // Modifier 'public' is redundant for interface fields
// private int k = 0; // Modifier 'private' not allowed here
// protected int l = 0; // Modifier 'protected' not allowed here
@@ -477,21 +504,21 @@ System.out.println(InterfaceExample.x);
- 从设计层面上看,抽象类提供了一种 IS-A 关系,那么就必须满足里式替换原则,即子类对象必须能够替换掉所有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系。
- 从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。
- 接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。
-- 接口的方法只能是 public 的,而抽象类的方法可以有多种访问权限。
+- 接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。
**4. 使用选择**
-使用抽象类:
-
-- 需要在几个相关的类中共享代码。
-- 需要能控制继承来的成员的访问权限,而不是都为 public。
-- 需要继承非静态(non-static)和非常量(non-final)字段。
-
使用接口:
- 需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Compareable 接口中的 compareTo() 方法;
- 需要使用多重继承。
+使用抽象类:
+
+- 需要在几个相关的类中共享代码。
+- 需要能控制继承来的成员的访问权限,而不是都为 public。
+- 需要继承非静态和非常量字段。
+
在很多情况下,接口优先于抽象类,因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。
- [深入理解 abstract class 和 interface](https://www.ibm.com/developerworks/cn/java/l-javainterface-abstract/)
@@ -499,8 +526,8 @@ System.out.println(InterfaceExample.x);
## super
-- 访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而完成一些初始化的工作。
-- 访问父类的成员:如果子类覆盖了父类的中某个方法的实现,可以通过使用 super 关键字来引用父类的方法实现。
+- 访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。
+- 访问父类的成员:如果子类重写了父类的中某个方法的实现,可以通过使用 super 关键字来引用父类的方法实现。
```java
public class SuperExample {
@@ -549,9 +576,22 @@ SuperExtendExample.func()
## 重写与重载
-- 重写(Override)存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法,子类的返回值类型要等于或者小于父类的返回值;
+**1. 重写(Override)**
-- 重载(Overload)存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。应该注意的是,返回值不同,其它都相同不算是重载。
+存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。
+
+为了满足里式替换原则,重写有有以下两个限制:
+
+- 子类方法的访问权限必须大于等于父类方法;
+- 子类方法的返回类型必须是父类方法返回类型或为其子类型。
+
+使用 @Override 注解,可以让编译器帮忙检查是否满足上面的两个限制条件。
+
+**2. 重载(Overload)**
+
+存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。
+
+应该注意的是,返回值不同,其它都相同不算是重载。
# 五、Object 通用方法
@@ -623,7 +663,7 @@ x.equals(null); // false;
**2. equals() 与 ==**
- 对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。
-- 对于引用类型,== 判断两个实例是否引用同一个对象,而 equals() 判断引用的对象是否等价,根据引用对象 equals() 方法的具体实现来进行比较。
+- 对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价。
```java
Integer x = new Integer(1);
@@ -636,7 +676,7 @@ System.out.println(x == y); // false
- 检查是否为同一个对象的引用,如果是直接返回 true;
- 检查是否是同一个类型,如果不是,直接返回 false;
-- 将 Object 实例进行转型;
+- 将 Object 对象进行转型;
- 判断每个关键域是否相等。
```java
@@ -667,11 +707,11 @@ public class EqualExample {
## hashCode()
-hasCode() 返回散列值,而 equals() 是用来判断两个实例是否等价。等价的两个实例散列值一定要相同,但是散列值相同的两个实例不一定等价。
+hashCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。
-在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个实例散列值也相等。
+在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象散列值也相等。
-下面的代码中,新建了两个等价的实例,并将它们添加到 HashSet 中。我们希望将这两个实例当成一样的,只在集合中添加一个实例,但是因为 EqualExample 没有实现 hasCode() 方法,因此这两个实例的散列值是不同的,最终导致集合添加了两个等价的实例。
+下面的代码中,新建了两个等价的对象,并将它们添加到 HashSet 中。我们希望将这两个对象当成一样的,只在集合中添加一个对象,但是因为 EqualExample 没有实现 hasCode() 方法,因此这两个对象的散列值是不同的,最终导致集合添加了两个等价的对象。
```java
EqualExample e1 = new EqualExample(1, 1, 1);
@@ -683,7 +723,7 @@ set.add(e2);
System.out.println(set.size()); // 2
```
-理想的散列函数应当具有均匀性,即不相等的实例应当均匀分布到所有可能的散列值上。这就要求了散列函数要把所有域的值都考虑进来,可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位。
+理想的散列函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的散列值上。这就要求了散列函数要把所有域的值都考虑进来,可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位。
一个数与 31 相乘可以转换成移位和减法:`31*x == (x<<5)-x`,编译器会自动进行这个优化。
@@ -725,7 +765,7 @@ ToStringExample@4554617c
**1. cloneable**
-clone() 是 Object 的 protect 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。
+clone() 是 Object 的 protected 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。
```java
public class CloneExample {
@@ -768,6 +808,8 @@ java.lang.CloneNotSupportedException: CloneExample
以上抛出了 CloneNotSupportedException,这是因为 CloneExample 没有实现 Cloneable 接口。
+应该注意的是,clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一个 protected 方法。Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException。
+
```java
public class CloneExample implements Cloneable {
private int a;
@@ -780,12 +822,9 @@ public class CloneExample implements Cloneable {
}
```
-应该注意的是,clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一个 protected 方法。Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException。
+**2. 浅拷贝**
-**2. 深拷贝与浅拷贝**
-
-- 浅拷贝:拷贝实例和原始实例的引用类型引用同一个对象;
-- 深拷贝:拷贝实例和原始实例的引用类型引用不同对象。
+拷贝对象和原始对象的引用类型引用同一个对象。
```java
public class ShallowCloneExample implements Cloneable {
@@ -825,6 +864,10 @@ e1.set(2, 222);
System.out.println(e2.get(2)); // 222
```
+**3. 深拷贝**
+
+拷贝对象和原始对象的引用类型引用不同对象。
+
```java
public class DeepCloneExample implements Cloneable {
private int[] arr;
@@ -868,6 +911,8 @@ e1.set(2, 222);
System.out.println(e2.get(2)); // 2
```
+**4. clone() 的替代方案**
+
使用 clone() 方法来拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。
```java
@@ -937,7 +982,7 @@ private 方法隐式地被指定为 final,如果在子类中定义的方法和
**1. 静态变量**
-- 静态变量:类所有的实例都共享静态变量,可以直接通过类名来访问它;静态变量在内存中只存在一份。
+- 静态变量:又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它;静态变量在内存中只存在一份。
- 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。
```java
@@ -956,7 +1001,7 @@ public class A {
**2. 静态方法**
-静态方法在类加载的时候就存在了,它不依赖于任何实例,所以静态方法必须有实现,也就是说它不能是抽象方法(abstract)。
+静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法(abstract)。
```java
public abstract class A {
@@ -996,7 +1041,6 @@ public class A {
A a2 = new A();
}
}
-
```
```html
@@ -1028,12 +1072,12 @@ public class OuterClass {
**5. 静态导包**
+在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低。
+
```java
import static com.xxx.ClassName.*
```
-在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低。
-
**6. 初始化顺序**
静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。
@@ -1075,11 +1119,12 @@ public InitialOrderTest() {
- 子类(实例变量、普通语句块)
- 子类(构造函数)
+
# 七、反射
每个类都有一个 **Class** 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。
-类加载相当于 Class 对象的加载。类在第一次使用时才动态加载到 JVM 中,可以使用 Class.forName("com.mysql.jdbc.Driver") 这种方式来控制类的加载,该方法会返回一个 Class 对象。
+类加载相当于 Class 对象的加载。类在第一次使用时才动态加载到 JVM 中,可以使用 `Class.forName("com.mysql.jdbc.Driver")` 这种方式来控制类的加载,该方法会返回一个 Class 对象。
反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。
diff --git a/notes/Java 容器.md b/notes/Java 容器.md
index bf1921fc..001f864c 100644
--- a/notes/Java 容器.md
+++ b/notes/Java 容器.md
@@ -21,17 +21,17 @@
# 一、概览
-容器主要包括 Collection 和 Map 两种,Collection 又包含了 List、Set 以及 Queue。
+容器主要包括 Collection 和 Map 两种,Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表。
## Collection
-
+
### 1. Set
-- HashSet:基于哈希表实现,支持快速查找。但不支持有序性操作,例如根据一个范围查找元素的操作。并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的。
+- TreeSet:基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作。但是查找效率不如 HashSet,HashSet 查找的时间复杂度为 O(1),TreeSet 则为 O(logN)。
-- TreeSet:基于红黑树实现,支持有序性操作,但是查找效率不如 HashSet,HashSet 查找时间复杂度为 O(1),TreeSet 则为 O(logN)。
+- HashSet:基于哈希表实现,支持快速查找,但不支持有序性操作。并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的。
- LinkedHashSet:具有 HashSet 的查找效率,且内部使用双向链表维护元素的插入顺序。
@@ -53,13 +53,14 @@
-- HashMap:基于哈希表实现;
+- TreeMap:基于红黑树实现。
+
+- HashMap:基于哈希表实现。
- HashTable:和 HashMap 类似,但它是线程安全的,这意味着同一时刻多个线程可以同时写入 HashTable 并且不会导致数据不一致。它是遗留类,不应该去使用它。现在可以使用 ConcurrentHashMap 来支持线程安全,并且 ConcurrentHashMap 的效率会更高,因为 ConcurrentHashMap 引入了分段锁。
- LinkedHashMap:使用双向链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序。
-- TreeMap:基于红黑树实现。
# 二、容器中的设计模式
@@ -129,12 +130,67 @@ private static final int DEFAULT_CAPACITY = 10;
ArrayList 基于数组实现,并且具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。
-保存元素的数组 elementData 使用 transient 修饰,该关键字声明数组默认不会被序列化。ArrayList 重写了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。
+保存元素的数组 elementData 使用 transient 修饰,该关键字声明数组默认不会被序列化。
```java
transient Object[] elementData; // non-private to simplify nested class access
```
+ArrayList 实现了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。
+
+```java
+private void readObject(java.io.ObjectInputStream s)
+ throws java.io.IOException, ClassNotFoundException {
+ elementData = EMPTY_ELEMENTDATA;
+
+ // Read in size, and any hidden stuff
+ s.defaultReadObject();
+
+ // Read in capacity
+ s.readInt(); // ignored
+
+ if (size > 0) {
+ // be like clone(), allocate array based upon size not capacity
+ ensureCapacityInternal(size);
+
+ Object[] a = elementData;
+ // Read in all elements in the proper order.
+ for (int i=0; i> 1)`,也就是旧容量的 1.5 倍。
@@ -241,14 +297,14 @@ public synchronized E get(int index) {
}
```
-### 2. 与 ArrayList 的区别
+### 2. 与 ArrayList 的比较
- Vector 是同步的,因此开销就比 ArrayList 要大,访问速度更慢。最好使用 ArrayList 而不是 Vector,因为同步操作完全可以由程序员自己来控制;
- Vector 每次扩容请求其大小的 2 倍空间,而 ArrayList 是 1.5 倍。
### 3. 替代方案
-为了获得线程安全的 ArrayList,可以使用 `Collections.synchronizedList();` 得到一个线程安全的 ArrayList。
+可以使用 `Collections.synchronizedList();` 得到一个线程安全的 ArrayList。
```java
List list = new ArrayList<>();
@@ -267,7 +323,7 @@ List list = new CopyOnWriteArrayList<>();
写操作在一个复制的数组上进行,读操作还是在原始数组中进行,读写分离,互不影响。
-写操作需要加锁,防止同时并发写入时导致的写入数据丢失。
+写操作需要加锁,防止并发写入时导致写入数据丢失。
写操作结束之后需要把原始数组指向新的复制数组。
@@ -331,9 +387,9 @@ transient Node first;
transient Node last;
```
-
+
-### 2. ArrayList 与 LinkedList
+### 2. 与 ArrayList 的比较
- ArrayList 基于动态数组实现,LinkedList 基于双向链表实现;
- ArrayList 支持随机访问,LinkedList 不支持;
@@ -351,7 +407,7 @@ transient Node last;
transient Entry[] table;
```
-其中,Entry 就是存储数据的键值对,它包含了四个字段。从 next 字段我们可以看出 Entry 是一个链表,即数组中的每个位置被当成一个桶,一个桶存放一个链表,链表中存放哈希值相同的 Entry。也就是说,HashMap 使用拉链法来解决冲突。
+Entry 存储着键值对。它包含了四个字段,从 next 字段我们可以看出 Entry 是一个链表。即数组中的每个位置被当成一个桶,一个桶存放一个链表。HashMap 使用拉链法来解决冲突,同一个链表中存放哈希值相同的 Entry。
@@ -579,8 +635,8 @@ y&(x-1) : 00000010
这个性质和 y 对 x 取模效果是一样的:
```
-x : 00010000
y : 10110010
+x : 00010000
y%x : 00000010
```
@@ -638,7 +694,7 @@ void addEntry(int hash, K key, V value, int bucketIndex) {
}
```
-扩容使用 resize() 实现,需要注意的是,扩容操作同样需要把旧 table 的所有键值对重新插入新的 table 中,因此这一步是很费时的。
+扩容使用 resize() 实现,需要注意的是,扩容操作同样需要把 oldTable 的所有键值对重新插入 newTable 中,因此这一步是很费时的。
```java
void resize(int newCapacity) {
@@ -684,7 +740,10 @@ capacity : 00010000
new capacity : 00100000
```
-对于一个 Key,它的哈希值如果在第 6 位上为 0,那么取模得到的结果和之前一样;如果为 1,那么得到的结果为原来的结果 +16。
+对于一个 Key,
+
+- 它的哈希值如果在第 5 位上为 0,那么取模得到的结果和之前一样;
+- 如果为 1,那么得到的结果为原来的结果 +16。
### 7. 扩容-计算数组容量
@@ -723,7 +782,7 @@ static final int tableSizeFor(int cap) {
从 JDK 1.8 开始,一个桶存储的链表长度大于 8 时会将链表转换为红黑树。
-### 9. HashMap 与 HashTable
+### 9. 与 HashTable 的比较
- HashTable 使用 synchronized 来进行同步。
- HashMap 可以插入键为 null 的 Entry。
@@ -884,7 +943,7 @@ transient LinkedHashMap.Entry head;
transient LinkedHashMap.Entry tail;
```
-accessOrder 决定了顺序,默认为 false,此时使用的是插入顺序。
+accessOrder 决定了顺序,默认为 false,此时维护的是插入顺序。
```java
final boolean accessOrder;
@@ -899,7 +958,7 @@ void afterNodeInsertion(boolean evict) { }
### afterNodeAccess()
-当一个节点被访问时,如果 accessOrder 为 true,则会将 该节点移到链表尾部。也就是说指定为 LRU 顺序之后,在每次访问一个节点时,会将这个节点移到链表尾部,保证链表尾部是最近访问的节点,那么链表首部就是最近最久未使用的节点。
+当一个节点被访问时,如果 accessOrder 为 true,则会将该节点移到链表尾部。也就是说指定为 LRU 顺序之后,在每次访问一个节点时,会将这个节点移到链表尾部,保证链表尾部是最近访问的节点,那么链表首部就是最近最久未使用的节点。
```java
void afterNodeAccess(Node e) { // move node to last
@@ -948,8 +1007,8 @@ removeEldestEntry() 默认为 false,如果需要让它为 true,需要继承
```java
protected boolean removeEldestEntry(Map.Entry eldest) {
- return false;
- }
+ return false;
+}
```
### LRU 缓存
@@ -957,7 +1016,7 @@ protected boolean removeEldestEntry(Map.Entry eldest) {
以下是使用 LinkedHashMap 实现的一个 LRU 缓存:
- 设定最大缓存空间 MAX_ENTRIES 为 3;
-- 使用 LinkedHashMap 的构造函数将 accessOrder 设置为 true,开启 LUR 顺序;
+- 使用 LinkedHashMap 的构造函数将 accessOrder 设置为 true,开启 LRU 顺序;
- 覆盖 removeEldestEntry() 方法实现,在节点多于 MAX_ENTRIES 就会将最近最久未使用的数据移除。
```java
@@ -1004,14 +1063,14 @@ private static class Entry extends WeakReference