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..adf899fa 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,7 +576,7 @@ SuperExtendExample.func()
## 重写与重载
-- 重写(Override)存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法,子类的返回值类型要等于或者小于父类的返回值;
+- 重写(Override)存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。子类的返回值类型要等于或者小于父类的返回值;
- 重载(Overload)存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。应该注意的是,返回值不同,其它都相同不算是重载。
@@ -623,7 +650,7 @@ x.equals(null); // false;
**2. equals() 与 ==**
- 对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。
-- 对于引用类型,== 判断两个实例是否引用同一个对象,而 equals() 判断引用的对象是否等价,根据引用对象 equals() 方法的具体实现来进行比较。
+- 对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价。
```java
Integer x = new Integer(1);
@@ -636,7 +663,7 @@ System.out.println(x == y); // false
- 检查是否为同一个对象的引用,如果是直接返回 true;
- 检查是否是同一个类型,如果不是,直接返回 false;
-- 将 Object 实例进行转型;
+- 将 Object 对象进行转型;
- 判断每个关键域是否相等。
```java
@@ -667,11 +694,11 @@ public class EqualExample {
## hashCode()
-hasCode() 返回散列值,而 equals() 是用来判断两个实例是否等价。等价的两个实例散列值一定要相同,但是散列值相同的两个实例不一定等价。
+hasCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。
-在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个实例散列值也相等。
+在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象散列值也相等。
-下面的代码中,新建了两个等价的实例,并将它们添加到 HashSet 中。我们希望将这两个实例当成一样的,只在集合中添加一个实例,但是因为 EqualExample 没有实现 hasCode() 方法,因此这两个实例的散列值是不同的,最终导致集合添加了两个等价的实例。
+下面的代码中,新建了两个等价的对象,并将它们添加到 HashSet 中。我们希望将这两个对象当成一样的,只在集合中添加一个对象,但是因为 EqualExample 没有实现 hasCode() 方法,因此这两个对象的散列值是不同的,最终导致集合添加了两个等价的对象。
```java
EqualExample e1 = new EqualExample(1, 1, 1);
@@ -683,7 +710,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 +752,7 @@ ToStringExample@4554617c
**1. cloneable**
-clone() 是 Object 的 protect 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。
+clone() 是 Object 的 protected 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。
```java
public class CloneExample {
@@ -768,6 +795,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 +809,9 @@ public class CloneExample implements Cloneable {
}
```
-应该注意的是,clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一个 protected 方法。Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException。
+**2. 浅拷贝**
-**2. 深拷贝与浅拷贝**
-
-- 浅拷贝:拷贝实例和原始实例的引用类型引用同一个对象;
-- 深拷贝:拷贝实例和原始实例的引用类型引用不同对象。
+拷贝对象和原始对象的引用类型引用同一个对象。
```java
public class ShallowCloneExample implements Cloneable {
@@ -825,6 +851,10 @@ e1.set(2, 222);
System.out.println(e2.get(2)); // 222
```
+**3. 深拷贝**
+
+拷贝对象和原始对象的引用类型引用不同对象。
+
```java
public class DeepCloneExample implements Cloneable {
private int[] arr;
@@ -868,6 +898,8 @@ e1.set(2, 222);
System.out.println(e2.get(2)); // 2
```
+**4. clone() 的替代方案**
+
使用 clone() 方法来拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。
```java
@@ -937,7 +969,7 @@ private 方法隐式地被指定为 final,如果在子类中定义的方法和
**1. 静态变量**
-- 静态变量:类所有的实例都共享静态变量,可以直接通过类名来访问它;静态变量在内存中只存在一份。
+- 静态变量:又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它;静态变量在内存中只存在一份。
- 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。
```java
@@ -956,7 +988,7 @@ public class A {
**2. 静态方法**
-静态方法在类加载的时候就存在了,它不依赖于任何实例,所以静态方法必须有实现,也就是说它不能是抽象方法(abstract)。
+静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法(abstract)。
```java
public abstract class A {
@@ -996,7 +1028,6 @@ public class A {
A a2 = new A();
}
}
-
```
```html
@@ -1028,12 +1059,12 @@ public class OuterClass {
**5. 静态导包**
+在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低。
+
```java
import static com.xxx.ClassName.*
```
-在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低。
-
**6. 初始化顺序**
静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。
@@ -1075,11 +1106,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 bd4ecfdb..9959e2ca 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,
+
+- 它的哈希值如果在第 6 位上为 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