必知必會之 Java
阿新 • • 發佈:2021-02-25
# 必知必會之 Java
## 目錄
不定期更新中……
[基礎知識](#基礎知識)
- [資料計量單位](#資料計量單位)
- [面向物件三大特性](#面向物件三大特性)
- [基礎資料型別](#基礎資料型別)
- [註釋格式](#註釋格式)
- [訪問修飾符](#訪問修飾符)
- [運算子](#運算子)
- [算數運算子](#算數運算子)
- [關係運算符](#關係運算符)
- [位運算子](#位運算子)
- [邏輯運算子](#邏輯運算子)
- [賦值運算子](#賦值運算子)
- [三目表示式](#三目表示式)
- [運算子優先順序](#運算子優先順序)
- [拷貝](拷貝)
- [什麼是淺拷貝?](#什麼是淺拷貝?)
- [什麼是淺拷貝?](#什麼是淺拷貝?)
- [重寫與過載](#重寫與過載)
- [什麼是重寫?](#什麼是重寫?)
- [什麼是過載?](#什麼是過載?)
- [重寫與過載的區別](#重寫與過載的區別)
- [引用傳遞與值傳遞](#引用傳遞與值傳遞)
- [類載入機制](#類載入機制)
- [什麼是雙親委派?](#什麼是雙親委派?)
- [如何打破雙親委派?](#如何打破雙親委派?)
- [Java 記憶體模型](#Java 記憶體模型)
- [快取一致性協議](#快取一致性協議)
- [快取行](#快取行)
- [快取行對齊](#快取行對齊)
- [合併寫](#合併寫)
- [Java 記憶體模型包括哪些東西?](#Java 記憶體模型包括哪些東西?)
- [Java 記憶體模型中,哪些物件是現場私有的?哪些物件是執行緒公有的?](#Java 記憶體模型中,哪些物件是現場私有的?哪些物件是執行緒公有的?)
- [Java 八大原子操作](#Java 八大原子操作)
[集合](#集合)
- [List](#List)
- [ArrayList 的底層實現](#ArrayList 的底層實現)
- [ArrayList 如何擴容?](#ArrayList 如何擴容?)
- [Map](#Map)
- [HashMap 的底層實現](#HashMap 的底層實現)
- [JDK 1.8 中 HashMap 為什麼要引入紅黑樹?](#JDK 1.8 中 HashMap 為什麼要引入紅黑樹?)
- [HashMap 什麼情況使用連結串列?什麼情況會使用紅黑樹?](#HashMap 什麼情況使用連結串列?什麼情況會使用紅黑樹?)
[IO 流](#IO 流)
- [IO 流的種類](#IO 流的種類)
- [常見的 IO 流](#常見的 IO 流)
- [BIO、NIO、AIO](#BIO、NIO、AIO)
- [NIO 的組成](#NIO 的組成)
- [零拷貝](#零拷貝)
[反射](#反射)
- [反射的實現原理](#反射的實現原理)
- [註解的實現原理](#註解的實現原理)
- [反射是否可以呼叫私有方法、獲取引數名、獲取父類私有方法?](#反射是否可以呼叫私有方法、獲取引數名、獲取父類私有方法?)
[其他](#其他)
- [將 ASCII 碼轉成字元](#將 ASCII 碼轉成字元)
- [獲取 Class 物件的方法](#獲取class物件的方法)
- [什麼是位元組碼?位元組碼有哪些使用場景?](#什麼是位元組碼?位元組碼有哪些使用場景?)
- [什麼是位元組碼增強技術?位元組碼增強有哪些使用場景?](#什麼是位元組碼增強技術?位元組碼增強有哪些使用場景?)
## 基礎知識
### 資料計量單位
8bit(位)=1Byte(位元組)
1024Byte(位元組)=1KB
1024KB=1MB
1024MB=1GB
1024GB=1TB
1024TB=PB
1024PB=1EB
1024EB=1ZB
1024ZB=1YB
1024YB=1BB
### 面向物件三大特性
封裝:**隱藏不想對外暴露的資訊**,提高安全性;**抽取公共程式碼**,提高可複用性。
繼承:**繼承為類的擴充套件提供了一種方式**。有利於修改公共屬性或方法,父類修改,所有子類無需重複修改。
多型:類的**多型體現在重寫和過載**,重寫通過繼承來實現,過載通過相同方法的不同引數來實現。
### 基礎資料型別
| 資料型別 | 位數 | 取值範圍 | 可轉型別 |
| -------- | ---- | ------------------------------------------------------------ | ------------------------ |
| byte | 8 | -128 ~ 127(-2^7 ~ 2^7-1) | |
| short | 16 | -32,768 ~ 32,767(-2^15 ~ 2^15-1) | int、long、float、double |
| int | 32 | -2,147,483,648 ~ 2,147,483,647(-2^31 ~ 2^31-1) | long、float、double |
| long | 64 | -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807(-2^63 ~ 2^63-1) | int、long、float、double |
| float | 32 | | double |
| double | 64 | | |
| char | 16 | \u0000 ~ \uffff(65 ~ 535) | int、long、float、double |
| boolean | 1 | true、false | |
### 註釋格式
- 單行註釋:
```java
// this is a comment
```
- 多行註釋:
```java
/* this is a comment */
```
- 文件註釋:
```java
/**
* this is a comment
*/
```
### 訪問修飾符
| 修飾符 | 當前類 | 同包 | 子類 | 其他包 |
| --------- | ------ | ---- | ---- | ------ |
| private | √ | × | × | × |
| default | √ | √ | × | × |
| protected | √ | √ | √ | × |
| public | √ | √ | √ | √ |
### 運算子
#### 算數運算子
| 操作符 | 描述 | 例子 |
| :----- | :-------------------------------- | :--------------------- |
| + | 加法 - 相加運算子兩側的值 | A + B = 30 |
| - | 減法 - 左運算元減去右運算元 | A – B = -10 |
| * | 乘法 - 相乘操作符兩側的值 | A * B = 200 |
| / | 除法 - 左運算元除以右運算元 | B / A = 2 |
| % | 取餘 - 左運算元除以右運算元的餘數 | B % A = 0 |
| ++ | 自增: 運算元的值增加1 | B++ = 21 或 ++B = 21 |
| -- | 自減: 運算元的值減少1 | B-- == 19 或 --B == 19 |
#### 關係運算符
| 運算子 | 描述 | 例子 |
| :----- | :----------------------------------------------------------- | :-------------- |
| == | 檢查如果兩個運算元的值是否相等,如果相等則條件為真。 | (A == B) 為假。 |
| != | 檢查如果兩個運算元的值是否相等,如果值不相等則條件為真。 | (A != B) 為真。 |
| > | 檢查左運算元的值是否大於右運算元的值,如果是那麼條件為真。 | (A > B) 為假。 |
| < | 檢查左運算元的值是否小於右運算元的值,如果是那麼條件為真。 | (A < B) 為真。 |
| >= | 檢查左運算元的值是否大於或等於右運算元的值,如果是那麼條件為真。 | (A >= B) 為假。 |
| <= | 檢查左運算元的值是否小於或等於右運算元的值,如果是那麼條件為真。 | (A <= B) 為真。 |
#### 位運算子
| 操作符 | 描述 | 例子 |
| :----- | :----------------------------------------------------------- | :----------------------------- |
| & | 與。如果相對應位都是 1,則結果為 1,否則為 0 | (A & B) 得到12,即 0000 1100 |
| \| | 或。如果相對應位都是 0,則結果為 0,否則為 1 | (A \| B) 得到 61,即 0011 1101 |
| ^ | 異或。如果相對應位值相同,則結果為 0,否則為1 | (A ^ B) 得到 49,即 0011 0001 |
| ~ | 取反。翻轉運算元的每一位,即 0 變成 1,1 變成 0。 | (~A) 得到 -61,即 1100 0011 |
| << | 左移。左運算元按位左移右運算元指定的位數。 | A << 2 得到 240,即 1111 0000 |
| >> | 右移。左運算元按位右移右運算元指定的位數。 | A >> 2 得到 15,即 1111 |
| >>> | 無符號右移。左運算元的值按右運算元指定的位數右移,移動得到的空位以零填充。 | A>>>2 得到 15,即 0000 1111 |
#### 邏輯運算子
| 操作符 | 描述 | 例子 |
| :----- | :----------------------------------------------------------- | :---------------- |
| && | 邏輯與,也稱短路與。當且僅當兩個運算元都為真,條件才為真。若第一個運算元為假,則第二個運算元不再判斷。 | (A && B) 為假。 |
| \|\| | 邏輯或,也稱短路或。如果任何兩個運算元任何一個為真,條件為真。若第一個運算元為假,則第二個運算元不再判斷。 | (A \|\| B) 為真。 |
| ! | 邏輯非。用來反轉運算元的邏輯狀態。如果條件為 true,則使用邏輯非運算子將得到 false。 | !(A && B) 為真。 |
#### 賦值運算子
| 操作符 | 描述 | 例子 |
| :----- | :----------------------------------------------------------- | :-------------------------------------- |
| = | 簡單的賦值運算子,將右運算元的值賦給左側運算元 | C = A + B 將把 A + B 得到的值賦給 C |
| += | 加和賦值操作符,它把左運算元和右運算元相加賦值給左運算元 | C += A 等價於 C = C + A |
| -= | 減和賦值操作符,它把左運算元和右運算元相減賦值給左運算元 | C -= A 等價於 C = C - A |
| *= | 乘和賦值操作符,它把左運算元和右運算元相乘賦值給左運算元 | C *= A 等價於 C = C * A |
| /= | 除和賦值操作符,它把左運算元和右運算元相除賦值給左運算元 | C /= A,C 與 A 同類型時等價於 C = C / A |
| %= | 取模和賦值操作符,它把左運算元和右運算元取模後賦值給左運算元 | C %= A 等價於 C = C % A |
| <<= | 左移位賦值運算子 | C <<= 2 等價於 C = C << 2 |
| >>= | 右移位賦值運算子 | C >>= 2 等價於 C = C >> 2 |
| &= | 按位與賦值運算子 | C &= 2 等價於 C = C & 2 |
| ^= | 按位異或賦值操作符 | C ^ = 2 等價於 C = C ^ 2 |
| \|= | 按位或賦值操作符 | C \| = 2 等價於 C = C \| 2 |
#### 三目表示式
```java
// 若 a == b 成立,返回 true,否則返回 false
boolean flag = (a == b) ? true : false
```
#### 運算子優先順序
> 所謂“好記性不如爛筆頭”。實際開發中,儘量使用括號來明確優先順序,提高程式碼可讀性,而非使用複雜的運算子複合運算。
>
> 如:((x++) && (y + 1) || z == 0)
| 優先順序 | 運算子 | 結合性 |
| ------ | ------------------------------------------------ | -------- |
| 1 | ()、[]、{} | 從左向右 |
| 2 | !、+、-、~、++、-- | 從右向左 |
| 3 | *、/、% | 從左向右 |
| 4 | +、- | 從左向右 |
| 5 | «、»、>>> | 從左向右 |
| 6 | <、<=、>、>=、instanceof | 從左向右 |
| 7 | ==、!= | 從左向右 |
| 8 | & | 從左向右 |
| 9 | ^ | 從左向右 |
| 10 | \| | 從左向右 |
| 11 | && | 從左向右 |
| 12 | \|\| | 從左向右 |
| 13 | ?: | 從右向左 |
| 14 | =、+=、-=、*=、/=、&=、\|=、^=、~=、«=、»=、>>>= | 從右向左 |
### 拷貝
#### 什麼是淺拷貝?
被複制物件的所有變數值與原物件相同,但引用變數仍然指向原來的物件。即淺拷貝只複製物件本身,而不復制物件中引用的物件。
示例:
```java
Teacher teacher = new Teacher();
teacher.setName("趙大");
teacher.setAge(42);
Student student1 = new Student();
student1.setName("張三");
student1.setAge(21);
student1.setTeacher(teacher);
Student student2 = (Student) student1.clone();
System.out.println("李四");
System.out.println(student2.getName());
System.out.println(student2.getAge());
System.out.println(student2.getTeacher().getName());
System.out.println(student2.getTeacher().getAge());
System.out.println("修改老師的資訊後-------------");
// 修改老師名稱
teacher.setName("John");
// 兩個學生的老師均發生變化
System.out.println(student1.getTeacher().getName());
System.out.println(student2.getTeacher().getName());
```
#### 什麼是深拷貝?
深拷貝是一個整個獨立的物件拷貝,深拷貝會拷貝所有的屬性,並拷貝屬性指向的動態分配的記憶體。
深拷貝的方法包括:
1. 重寫 clone() 方法
```java
public class Student implements Cloneable {
private String name;
private Integer age;
private Teacher teacher;
// 省略 get/set 方法
@Override
protected Object clone() throws CloneNotSupportedException {
Student3 student = (Student3) super.clone();
// 複製一個新的 Teacher 物件例項,並設定到新的 student 物件例項中
student.setTeacher((Teacher2) student.getTeacher().clone());
return student;
}
}
```
2. 使用序列化實現
```java
public class Teacher implements Serializable {
private String name;
private int age;
// 省略 get/set 方法
}
class Student implements Serializable {
private String name;
private int age;
private Teacher3 teacher;
// 省略 get/set 方法
public Object deepClone() throws Exception {
// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
}
```
### 重寫與過載
#### 什麼是重寫?
重寫是指子類對父類允許訪問的方法進行重新編寫,返回值和形參都不能改變。**即方法入參出參不變,實現邏輯重寫**。
示例:
```java
public class OverrideParent {
public void m1(String var) {
System.out.println(var);
}
}
public class OverrideChild extends OverrideParent {
/**
* 重寫父類的 m1() 方法
*/
@Override
public void m1(String var) {
System.out.println(var);
}
/**
* 子類的 m1() 方法:與父類 m1() 方法的形參不同
*/
public void m1(int var) {
System.out.println(var);
}
/**
* 子類的 m1() 方法:與父類 m1() 方法的形參、返回值不同
*/
public String m1() {
System.out.println("var");
return "var";
}
}
```
#### 什麼是過載?
過載是指一個類中存在多個同名方法,且方法的形參不同。**即方法名稱相同、形參不同**。
示例:
```java
public class OverloadClass {
public void m1() {
System.out.println("key");
}
public void m1(String key) {
System.out.println(key);
}
public void m1(String key, Integer value) {
System.out.println(key);
}
}
```
#### 重寫與過載的區別
1. 重寫要求方法名、入參、返回值相同,過載只同名方法的入參不同(型別、個數、順序至少有一個不同)。
2. 重寫要求子類不能縮小父類方法的訪問許可權,過載與訪問許可權無關。
3. 重寫要求子類方法不能丟擲比父類方法更多的異常(但子類方法可以不丟擲異常),過載與異常範圍無關。
4. 重寫是子類對父類方法的覆蓋行為,過載是一個類的多型性。
5. 重寫方法不能被定義為 final,過載方法可以被定義為 final。
### 類載入機制
#### 什麼是雙親委派?
在類載入過程中,子類會先去父類查詢,如果找到,則從父類快取載入。如果沒找到,再由父類指派子類進行載入。
雙親委派機制主要出於安全來考慮。比如自定義 java.lang.String,如果不先去父類查詢,相當於 Bootstrap 載入器的 java.lang.String 被篡改了。
#### 如何打破雙親委派?
1. 重寫 loadClass() 方法
> JDK 1.2 之前,自定義 ClassLoader 都必須重寫 loadClass()
2. ThreadContextClassLoader 可以實現基礎類呼叫實現類程式碼,通過 thread.setContextClassLoader 指定
3. 熱啟動,熱部署
> OSGI、Tomcat 都有自己的模組指定 Classloader(可以載入同一類庫的不同版本)
### Java 記憶體模型
#### 快取一致性協議
現代 CPU 的資料一致性實現 = 快取鎖(MESI 等) + 匯流排鎖。快取一致性協議一般是指快取鎖層面的協議,目前快取一致性協議的實現有很多種,比較常見的就是 Intel 所使用 **MESI** 協議。
MESI 協議定義了四種狀態,分別是 Modified、Exclusive、Shared 和 Invalid。
- Modified 狀態:該Cache line有效,資料被修改且未同步到記憶體,資料和記憶體資料不一致,資料只存在於本 Cache 中。
- Exclusive 狀態:該Cache line有效,資料由單 CPU 獨佔,資料和記憶體資料一致,資料只存在於本 Cache 中。
- Shared 狀態:該Cache line有效,資料由所有 CPU 共享,資料和記憶體資料一致,資料存在於所有 Cache 中。
- Invalid 狀態:該Cache line無效。
#### 快取行
讀取快取以 Cache Line 為基本單位,目前 64 bytes。
位於同一快取行的兩個不同資料,被兩個不同 CPU 鎖定,產生互相影響的偽共享問題,使用快取行的對齊能夠有效解決偽共享問題,提高處理效率。
#### 快取行的對齊
```java
//一個快取行64個位元組,設定56個的佔位符,令要插入的資料單獨佔用一行快取行
public static class Padding {
public volatile long p1,p2,p3,p4,p5,p6,p7;
}
public static class T extends Padding {
public volatile long x = 0L;
}
public static T[] arr = new T[2];
static {
arr[0] = new T();
arr[1] = new T();
}
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
for (long i = 0; i < 1000_0000L; i++) {
arr[0].x = i;
}
});
Thread t2 = new Thread(() -> {
for (long i = 0; i < 1000_0000L; i++) {
arr[0].x = i;
}
});
t1.start();
t2.start();
t1.join();
t1.join();
}
```
- 使用快取行對齊的開源軟體:Disruptor(號稱單機效率最高的佇列)
#### 合併寫
如果 CPU 需要訪問的地址 hash 之後並不在快取行(cache line)中,那麼快取中對應位置的快取行(cache line)會失效,以便讓新的值可以取代該位置的現有值。例如,如果我們有兩個地址,通過 hash 演算法 hash 到同一快取行,那麼新的值會覆蓋老的值。
當 CPU 執行儲存指令(store)時,它會嘗試將資料寫到離 CPU 最近的 L1 快取。如果這時出現快取失效,CPU 會訪問下一級快取。這時無論是英特爾還是許多其他廠商的 CPU 都會使用被稱為“合併寫(write combining)”的技術。
當請求 L2 快取行的所有權的時候,最典型的是將處理器的 store buffers 中某一項寫入記憶體的期間, 在快取子系統(cache sub-system)準備好接收、處理的資料的期間,CPU 可以繼續處理其他指令。當資料不在任何快取層中快取時,將獲得最大的優勢。
當連串的寫操作需要修改相同的快取行時,會變得非常有趣。在修改提交到 L2 快取之前,這連串的寫操作會首先合併到緩衝區(buffer)。 這些 64 位元組的緩衝(buffers )維護在一個 64 位的區域中,每一個位元組(byte)對應一個位(bit),當緩衝區被傳輸到外快取後,標誌快取是否有效。隨後,硬體在讀取快取之前會先讀取緩衝區。
如果我們可以在緩衝區被傳輸到外快取之前能夠填補這些緩衝區(buffers ),那麼我們將大大提高傳輸匯流排的效率。由於這些緩衝區的數量是有限的,並且它們根據 CPU 的型號有所不同。例如在 Intel CPU,你只能保證在同一時間拿到 4 個。這意味著,在一個迴圈中,你不應該同時寫超過 4 個截然不同的記憶體位置,否則你講不能從合併寫(write combining)的中受益。
#### Java 記憶體模型包括哪些東西?
程式計數器、方法區、本地方法棧、虛擬機器方法棧、堆。
#### Java 記憶體模型中,哪些物件是執行緒私有的?哪些物件是執行緒公有的?
程式計數器、本地方法棧、虛擬機器方法棧是執行緒私有的,方法區、堆是執行緒公有的。
#### 如何保證特定情況下不亂序
**硬體層面:使用記憶體屏障**
- **sfence**: store| 在sfence指令前的寫操作當必須在sfence指令後的寫操作前完成。
- **lfence**:load | 在lfence指令前的讀操作當必須在lfence指令後的讀操作前完成。
- **mfence**:mix | 在mfence指令前的讀寫操作當必須在mfence指令後的讀寫操作前完成。
> 原子指令,如x86上的”lock …” 指令是一個Full Barrier,執行時會鎖住記憶體子系統來確保執行順序,甚至跨多個CPU。Software Locks通常使用了記憶體屏障或原子指令來實現變數可見性和保持程式順序
**JVM層面:使用 JSR133 規範**
- LoadLoad屏障:
對於這樣的語句 Load1; LoadLoad; Load2, 在 Load2 及後續讀取操作要讀取的資料被訪問前,保證 Load1 要讀取的資料被讀取完畢。
- StoreStore屏障:
對於這樣的語句 Store1; StoreStore; Store2,在 Store2 及後續寫入操作執行前,保證 Store1 的寫入操作對其它處理器可見。
- LoadStore屏障:
對於這樣的語句 Load1; LoadStore; Store2,在 Store2 及後續寫入操作被刷出前,保證 Load1 要讀取的資料被讀取完畢。
- StoreLoad屏障:
對於這樣的語句 Store1; StoreLoad; Load2,在 Load2 及後續所有讀取操作執行前,保證 Store1 的寫入對所有處理器可見。
#### java 八大原子操作
> 最新的 JSR-133 已經放棄了這種描述,但 JMM 沒有變化。
**lock**:主記憶體,標識變數為執行緒獨佔
**unlock**:主記憶體,解鎖執行緒獨佔變數
**read**:主記憶體,讀取內容到工作記憶體
**write**:主記憶體,寫變數值
**load**:工作記憶體,read 後的值放入執行緒本地變數副本
**use**:工作記憶體,傳值給執行引擎
**assign**:工作記憶體,執行引擎結果賦值給執行緒本地變數
**store**:工作記憶體,存值到主記憶體給 write 備用
## 集合
### List
#### ArrayList 的底層實現
ArrayList 是基於陣列實現的,是一個動態陣列,其容量能自動增長,類似於 C 語言中的動態申請記憶體,動態增長記憶體。
ArrayList 不是執行緒安全的,只能用在單執行緒環境下,多執行緒環境下可以考慮用 Collections.synchronizedList(List l) 函式返回一個執行緒安全的 ArrayList 類,也可以使用併發包下的 CopyOnWriteArrayList 類。
#### ArrayList 如何擴容?
陣列進行擴容時,會將老陣列中的元素重新拷貝一份到新的陣列中,每次陣列容量的增長大約是其原容量的1.5倍。這種操作的代價是很高的,因此在實際使用時,我們應該儘量避免陣列容量的擴張。
當我們可預知要儲存的元素的多少時,要在構造 ArrayList 例項時,就指定其容量,以避免陣列擴容的發生。或者根據實際需求,通過呼叫 ensureCapacity 方法來手動增加 ArrayList 例項的容量。
### Map
#### HashMap 的底層實現
JDK 1.8 之前使用**陣列 + 單鏈表**實現,JDK 1.8 以後使用**陣列 + 單鏈表/紅黑樹**實現。
#### JDK 1.8 中 HashMap 為什麼要引入紅黑樹?
當 HashMap 中出現較多雜湊衝突時,連結串列有可能會變得非常長,而連結串列是從連結串列的 head 或者 tail 查詢的,效率會隨著長度的增長而降低。引入紅黑樹就是為了解決連結串列過長帶來的查詢效率問題。紅黑樹的樹形結構使原本查詢連結串列的時間複雜度 O(n) 降到了 O(logn)。
#### HashMap 什麼情況使用連結串列?什麼情況會使用紅黑樹?
若桶中連結串列元素超過 8 時,會自動轉化成紅黑樹;若桶中元素小於等於 6 時,樹結構還原成連結串列形式。
原因:
- 紅黑樹的平均查詢長度是log(n),長度為8,查詢長度為log(8)=3,連結串列的平均查詢長度為n/2,當長度為8時,平均查詢長度為8/2=4,這才有轉換成樹的必要。
- 連結串列長度如果是小於等於6,6/2=3,雖然速度也很快的,但是轉化為樹結構和生成樹的時間並不會太短。
- 中間有個差值7可以防止連結串列和樹之間頻繁的轉換。假設一下,如果設計成連結串列個數超過8則連結串列轉換成樹結構,連結串列個數小於8則樹結構轉換成連結串列,如果一個HashMap不停的插入、刪除元素,連結串列個數在8左右徘徊,就會頻繁的發生樹轉連結串列、連結串列轉樹,效率會很低。
## IO 流
### IO 流的種類
- 按照流的流向,可以分為**輸入流**和**輸出流**;
- 按照操作單元,可以分為**位元組流**和**字元流**;
- 按照流的角色,可以分為**節點流**和**處理流**。
### 常見的 IO 流
- 輸入流
- 位元組流
- FileInputStream
- PipedInputStream
- ByteArrayInputStream
- BUfferedInputStream
- DataInputStream
- ObjectInputStream
- SequenceInputStream
- 字元流
- FileReader
- PipedReader
- CharArrayReader
- BufferedReader
- InputStreamReader
- 輸出流
- 位元組流
- FileOutputStream
- PipedOutputStream
- ByteArrayOutputStream
- BufferedOutputStream
- DataOutputStream
- ObjectOutputStream
- PrintOutputStream
- 字元流(Writer)
- FileWriter
- PipedWriter
- CharArrayWriter
- BufferedWriter
- OutputStreamWriter
- PrintWriter
### BIO、NIO、AIO
- BIO 是指**同步阻塞 IO(Blocking I/O)**。一次資料的讀取或寫入會阻塞當前執行緒,直到本次資料傳輸結束。操作簡單,適合活動連線數較小的情況。
- NIO 是在 Java 1.4 中引入的新的 I/O 模型,因為被稱為 New IO。但隨著技術的快速發展,NIO 也不再“新”了,因此,我們現在更習慣以它的特性來稱其為:**同步非阻塞 IO(Non-Blocking I/O)**。NIO 提供了 Channel、Selector、Buffer 等抽象,實現了多路複用。此外,NIO 還提供了 SocketChannel 和 ServerSocketChannel 兩種不同的套接字通道實現,分別對應 BIO 中的 Socket 和 ServerSocket。
> **NIO 並非只是非阻塞的**,NIO 同時支援阻塞、非阻塞兩種模式,只是因為 NIO 主要就是為了提高 IO 效能而誕生的,所以強調了其核心特性:非阻塞。在日常使用中,我們也更為傾向於 NIO 的非阻塞模式,以獲得更高的吞吐量和併發量。
- AIO 是在 Java 7 中引入的**非同步非阻塞 IO(Asynchronous I/O)**。AIO 是基於事件和回撥機制實現的,當操作發生後,會直接得到返回,釋放 IO 資源,實際操作的執行則交給其他執行緒來處理,處理完成後通知相應的執行緒進行後續的操作。
### NIO 的組成
- 緩衝區(Buffer):用來儲存待傳輸的資料,通過 Channel 進行資料傳輸。
- 直接緩衝區(DirectByteBuffer):使用堆外記憶體建立的緩衝區,可以減少一次堆內記憶體到堆外記憶體的資料拷貝。
> 使用堆外記憶體建立和銷燬緩衝區的成本更高且不可控,通常會使用記憶體池來提高效能。
- 通道(Channel):用來建立資料傳輸需要的連線,並傳輸 Buffer 中的資料。
> 資料雖然需要通過 Channel 進行傳輸,但 Channel 是不直接操作資料的,Channel 只負責建立連線並確認傳輸內容,實際資料的傳輸是通過
- 選擇器(Selector):用來管理 Channel 和分配
### 零拷貝
在 Java 程式中,使用 **read() 或 write() 方法拷貝**,需要在堆內開闢記憶體空間儲存檔案流,再從堆內拷貝到堆外,最後從堆外拷貝到作業系統核心,由 DMA 讀寫到磁碟。期間需要經過兩次複製,且使用者態和核心態的互動,因此傳輸效率較慢。
而在作業系統中提供了 **mmap() 方法**,我們可以在程式中呼叫該方法,系統會直接在核心開闢記憶體空間,直接將檔案流傳輸到核心開闢出的記憶體空間,由 DMA 讀寫到磁碟。該方法通過減少檔案流的拷貝過程和使用者態、核心態的互動,從而提高了檔案傳輸的效率。我們把這種方法,稱為“零拷貝”。
當然,零拷貝雖然可以提高檔案傳輸效率,但也並非沒有缺點的。由於程式直接傳入核心記憶體空間,在發生 IO 異常、宕機等異常情況下,使用零拷貝有可能會導致資料流的丟失。
## 反射
### 反射的實現原理
在 Java 中是通過 Class.forName(classname) 來獲取類的資訊,實現反射機制的。
### 註解的實現原理
註解是基於 Java 反射來實現的。
### 反射是否可以呼叫私有方法、獲取引數名、獲取父類私有方法?
可以。我們可以通過反射拿到對應的 class 物件,然後通過 class.getDeclaredConstructors() 拿到全部構造器,獲取構造器的名稱、引數、修飾符等資訊;可以通過 class.getDeclaredMethods() 拿到全部方法,獲取方法的名稱、引數、修飾符等資訊;可以通過 class.getSuperclass().getDeclaredMethod() 獲取父類全部方法。
## 泛型
### 泛型的型別安全
JDK 1.5 以後引入了泛型的概念,通過泛型能夠幫助我們在程式處理中將處理邏輯抽象出來,提高程式碼複用性。泛型的使用一般遵循型別約束,以此保證泛型型別的安全性。舉個例子:
```java
clas