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