Base64 是一種編碼方法,用於將二進制資料轉換成64個可列印的ASCII字元的序列。這種編碼方式廣泛應用於在文字格式中儲存二進制資料,例如在 URL、檔名、或在 XML 和 JSON 文件中。Java 中的 Base64 編碼和解碼通常可以透過 java.util.Base64
類實現,這個類在 Java 8 及以後的版本中提供。
以下是V 哥整理的 Java 中 java.util.Base64
類的核心元件,V 哥將重點介紹這些元件的原理和用法:
編碼器(Encoder):
getEncoder()
:返回一個Base64.Encoder
例項,用於將資料編碼為 Base64 格式。解碼器(Decoder):
getDecoder()
:返回一個Base64.Decoder
例項,用於將 Base64 編碼的資料解碼回原始格式。編碼表(Encoding Table):
Base64 編碼器使用一個靜態的編碼表來將位元組對映到 Base64 字元。
解碼錶(Decoding Table):
Base64 解碼器使用一個靜態的解碼錶來將 Base64 字元對映回字節。
編碼模式(Encoding Mode):
BASE64
:標準的 Base64 編碼模式。URL_SAFE
:URL 和檔名安全的 Base64 編碼模式,使用-
和_
代替標準模式中的+
和/
。行長度和填充:
Base64 編碼輸出通常每76個字元會有一個換行符,但這可以透過
Encoder
的配置來改變。
示例程式碼:
import java.util.Base64; import java.nio.charset.StandardCharsets; public class Base64Example { public static void main(String[] args) { String original = "Hello, VG!"; byte[] encoded = Base64.getEncoder().encode(original.getBytes(StandardCharsets.UTF_8)); System.out.println("Encoded: " + new String(encoded, StandardCharsets.UTF_8)); byte[] decoded = Base64.getDecoder().decode(encoded); System.out.println("Decoded: " + new String(decoded, StandardCharsets.UTF_8)); } }
在上述程式碼中,我們使用 Base64.getEncoder().encode()
方法將字串 "Hello, VG!" 編碼為 Base64 格式,然後使用 Base64.getDecoder().decode()
方法將其解碼回原始字串。
Base64 編碼和解碼的實現通常依賴於這些核心元件,它們提供了靈活的方式來處理不同場景下的編碼需求。
小試牛刀後,我們來一起詳細看看它們的實現原理。
1. 編碼器(Encoder)
在 Java 8 引入的 java.util.Base64
包中,Encoder
類是 Base64
類的一個內部類,用於實現 Base64 編碼功能。以下是 Encoder
類實現的詳細步驟和原理分析:
1. 初始化編碼器
首先,透過 Base64.getEncoder()
獲取 Encoder
物件的例項。這個例項包含了編碼過程中需要的所有配置,例如是否新增填充字元等。
Base64.Encoder encoder = Base64.getEncoder();
2. 準備編碼資料
將需要編碼的資料放入位元組陣列中。這些資料將作為輸入傳遞給編碼器。
byte[] dataToEncode = "beijing Hot".getBytes(StandardCharsets.UTF_8);
3. 編碼資料
使用 Encoder
例項的 encode
方法對資料進行編碼。這個方法會返回一個包含 Base64 編碼結果的位元組陣列。
byte[] encodedData = encoder.encode(dataToEncode);
4. 處理編碼結果
編碼後的位元組陣列可以轉換為字串,或者直接寫入到輸出流中。
String encodedString = new String(encodedData, StandardCharsets.UTF_8);
編碼原理
Base64 字符集: Base64 編碼使用一個包含 64 個字元的字符集,包括大寫字母 A-Z、a-z、數字 0-9、加號(+)和斜槓(/)。
3 位元組到 4 字元的對映: 每次從輸入資料中讀取 3 個位元組(24 位),然後將這 24 位分割成 4 個 6 位的組。每個 6 位組對映到一個 Base64 字元。
填充: 如果輸入資料的位元組數不是 3 的倍數,在編碼的最後會新增一個或兩個
=
字元作為填充。換行符: 在編碼過程中,可以根據配置在每 76 個字元後新增換行符,以確保編碼後的文字符合 MIME 的要求。
無填充模式: 使用
encoder.withoutPadding()
可以禁用自動填充,這樣編碼後的輸出就不會包含=
字元。
原始碼分析
在 Base64.Encoder
類中,編碼過程主要涉及以下幾個關鍵部分:
編碼表:
ENCODE
陣列定義瞭如何將 6 位二進制數對映到 Base64 字符集。緩衝區:
encoder
內部維護一個緩衝區,用於儲存待編碼的位元組。編碼方法:
encode
方法實現具體的編碼邏輯,包括從緩衝區讀取位元組、對映到 Base64 字元、處理剩餘位元組和填充。
編碼步驟
填充緩衝區:將輸入資料寫入到
encoder
的內部緩衝區。分組:將緩衝區中的位元組按每 3 個位元組分為一組。
對映字元:使用
ENCODE
表將每組的 24 位對映到 4 個 Base64 字元。處理剩餘:如果最後一組不足 3 個位元組,使用
=
作為填充,並相應調整對映的字元。輸出結果:將對映後的字元輸出或轉換為字串。
來一個示例程式碼嘗試一下使用
public byte[] encode(byte[] input) { // 初始化輸出陣列 byte[] output = new byte[...]; int outputPos = 0; for (int i = 0; i < input.length; i += 3) { // 讀取 3 個位元組 int threeBytes = ((input[i] & 0xFF) << 16) | ((i + 1 < input.length) ? (input[i + 1] & 0xFF) << 8 : 0) | ((i + 2 < input.length) ? (input[i + 2] & 0xFF) : 0); // 對映到 4 個 Base64 字元 for (int j = 0; j < 4; j++) { int index = (threeBytes & (0xFF << (8 * (3 - j)))) >> (8 * (3 - j)); output[outputPos++] = ENCODE[index]; } } // 處理填充 if (neededPadding) { output[outputPos++] = '='; // 可能還需要第二個 '=' } return Arrays.copyOf(output, outputPos); }
以上程式碼演示了 Base64 編碼的基本邏輯,實際的 Encoder
類實現可能會包含更多的細節,例如處理換行符、無填充模式等。
2. 解碼器(Decoder)
在 Java 8 及以後的版本中,java.util.Base64
包中的 Decoder
類是 Base64
類的一個內部類,用於實現 Base64 解碼功能。以下是 Decoder
類實現的詳細步驟和原理分析:
1. 初始化解碼器
首先,透過 Base64.getDecoder()
獲取 Decoder
物件的例項。這個例項包含了解碼過程中需要的所有配置。
Base64.Decoder decoder = Base64.getDecoder();
2. 準備解碼資料
將需要解碼的 Base64 字串轉換為位元組陣列。這些資料將作為輸入傳遞給解碼器。
String base64String = "SGVsbG8sIFdvcmxkIQ=="; byte[] dataToDecode = base64String.getBytes(StandardCharsets.UTF_8);
3. 解碼資料
使用 Decoder
例項的 decode
方法對 Base64 字串進行解碼。這個方法會返回一個包含原始資料的位元組陣列。
byte[] decodedData = decoder.decode(dataToDecode);
4. 處理解碼結果
解碼後的位元組陣列可以轉換為字串,或者直接用於其他需要原始資料的場合。
String decodedString = new String(decodedData, StandardCharsets.UTF_8);
解碼原理
Base64 字符集: Base64 解碼使用與編碼相同的 64 個字符集,包括大寫字母 A-Z、a-z、數字 0-9、加號(+)和斜槓(/)。
4 字元到 3 位元組的對映: 每次從 Base64 編碼的資料中讀取 4 個字元(24 位),然後將這 24 位分割成 3 個 8 位的組。每個 8 位的組對映回原始的位元組。
處理填充: 如果編碼時使用了填充字元(
=
),解碼時需要識別並忽略這些字元。異常處理: 如果輸入資料包含非法字元或格式不正確,解碼過程將丟擲
IllegalArgumentException
。
原始碼分析
在 Base64.Decoder
類中,解碼過程主要涉及以下幾個關鍵部分:
解碼錶:
DECODE
陣列定義了 Base64 字元到 6 位二進制數的對映。緩衝區:
decoder
內部維護一個緩衝區,用於儲存待解碼的 Base64 字元。解碼方法:
decode
方法實現具體的解碼邏輯,包括從輸入讀取字元、對映回字節、處理填充和非法字元。
解碼步驟
填充緩衝區:將 Base64 編碼的字串轉換為位元組陣列,並填充到
decoder
的內部緩衝區。分組:將緩衝區中的 Base64 字元按每 4 個字元分為一組。
對映位元組:使用
DECODE
表將每組的 24 位對映回 3 個位元組。處理填充:如果編碼時使用了填充,解碼時識別
=
字元並相應調整對映的位元組。輸出結果:將對映後的位元組輸出或轉換為原始資料。
還是上示例看用法
public byte[] decode(byte[] input) { // 初始化輸出陣列 byte[] output = new byte[...]; int outputPos = 0; for (int i = 0; i < input.length; i += 4) { // 讀取 4 個 Base64 字元 int fourChars = (DECODE[input[i] & 0xFF] << 18) | (DECODE[input[i + 1] & 0xFF] << 12) | (DECODE[input[i + 2] & 0xFF] << 6) | (DECODE[input[i + 3] & 0xFF]); // 對映回 3 個位元組 output[outputPos++] = (fourChars >> 16) & 0xFF; if (input[i + 2] != '=') { output[outputPos++] = (fourChars >> 8) & 0xFF; } if (input[i + 3] != '=') { output[outputPos++] = fourChars & 0xFF; } } return Arrays.copyOf(output, outputPos); }
以上的程式碼演示了 Base64 解碼的基本邏輯,實際的 Decoder
類實現可能會包含更多的細節,例如處理非法字元、解碼錶的初始化等。
透過這種方式,Base64.Decoder
提供了一種靈活且高效的方式來將 Base64 編碼的字串解碼回原始的位元組資料,適用於多種不同的解碼需求。
3. 編碼表(Encoding Table)
Base64 編碼表是 Base64 編碼和解碼過程中的核心元件之一。它是一個查詢表,用於將 6 位二進制值對映到相應的 Base64 編碼字元。以下是 Base64 編碼表的原始碼實現過程步驟和原理分析:
1. 定義 Base64 字符集
Base64 編碼使用 64 個可列印的 ASCII 字元來表示資料。這些字元包括大寫字母 A-Z
(26 個)、小寫字母 a-z
(26 個,但在標準 Base64 中通常使用大寫)、數字 0-9
(10 個)、加號 +
和斜槓 /
。此外,爲了支援 URL 和檔名,還有一個變種使用 -
代替 +
和 _
代替 /
。
2. 初始化編碼表
在 Java 的 java.util.Base64
類中,編碼表通常是透過一個靜態初始化的陣列來實現的。這個陣列的長度為 64,正好對應於 Base64 字符集中的字元數量。
private static final char[] ENCODE = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', // ... 省略中間字元 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };
3. 使用編碼表進行編碼
在編碼過程中,原始資料被讀取並轉換為位元組,然後每三個位元組(24 位)被轉換為四個 6 位的組。每個 6 位組的值透過查詢編碼表來找到對應的 Base64 字元。
4. 處理填充
如果原始資料的長度不是 3 的倍數,最後一組可能只有 1 或 2 個位元組。在這種情況下,剩餘的位使用 =
字元填充。填充字元不透過編碼表對映,而是直接新增到輸出中。
5. 生成編碼字串
將對映得到的 Base64 字元連線起來,形成最終的編碼字串。
原理解釋
6 位對映:由於 Base64 編碼將 3 個位元組(24 位)轉換為 4 個字元(每個字元 6 位),所以每個字元可以表示 6 位二進制資料。
ENCODE
陣列中的每個索引值對應一個 6 位的二進制數。字元選擇:在編碼過程中,透過將位元組資料的位操作結果作為索引,從
ENCODE
陣列中選擇相應的字元。效能最佳化:使用查詢表可以快速地進行字元對映,避免了複雜的條件判斷或迴圈,從而提高了編碼的效率。
示例程式碼
以下是如何使用編碼表進行 Base64 編碼的示例程式碼:
public String encode(byte[] data) { StringBuilder encoded = new StringBuilder(); int i = 0; while (i < data.length - 2) { // 處理完整組 int threeBytes = ((data[i] & 0xFF) << 16) | ((data[i + 1] & 0xFF) << 8) | (data[i + 2] & 0xFF); // 將 24 位分為 4 個 6 位組 for (int j = 0; j < 4; j++) { int index = (threeBytes >> (18 - j * 6)) & 0x3F; encoded.append(ENCODE[index]); } i += 3; } // 處理剩餘位元組和填充 if (i < data.length) { int remaining = data.length - i; int twoBytes = (data[i] & 0xFF) << (8 * (2 - remaining)); for (int j = 0; j < remaining; j++) { int index = (twoBytes >> (16 - j * 8)) & 0xFF; encoded.append(ENCODE[index]); } // 新增填充字元 while (encoded.length() % 4 != 0) { encoded.append('='); } } return encoded.toString(); }
這個虛擬碼演示瞭如何使用 Base64 編碼表將位元組資料編碼為 Base64 字串。實際的 Base64.Encoder
類實現可能會包含更多的細節,例如處理換行符等。
4. 解碼錶(Decoding Table)
Base64 解碼錶是 Base64 編碼和解碼過程中的另一個核心元件。它用於將 Base64 編碼的字元對映回相應的 6 位二進制值。以下是 Base64 解碼錶的原始碼實現過程步驟和原理分析:
1. 定義 Base64 字符集
與編碼表一樣,解碼錶依賴於 Base64 字符集,包括大寫字母 A-Z
、小寫字母 a-z
(在標準 Base64 中通常不使用)、數字 0-9
、加號 +
和斜槓 /
。對於 URL 安全的 Base64,使用 -
代替 +
和 _
代替 /
。
2. 初始化解碼錶
在 Java 的 java.util.Base64
類中,解碼錶通常是透過一個靜態初始化的陣列來實現的。這個陣列的長度為 128,覆蓋了所有可能的 ASCII 字元,因為標準 ASCII 字符集大小為 128 個字元。
private static final int[] DECODE = new int[128];
在靜態初始化塊中,解碼錶被填充。每個 Base64 字元都被賦予一個值,從 0 到 63,而非法字元則通常被賦予 -1 或其他表示無效的值。
static { for (int i = 0; i < DECODE.length; i++) { DECODE[i] = -1; // 初始值設為無效 } // 為 Base64 字元賦值 for (int i = 'A'; i <= 'Z'; i++) { DECODE[i] = i - 'A'; } for (int i = 'a'; i <= 'z'; i++) { DECODE[i] = 26 + i - 'a'; } for (int i = '0'; i <= '9'; i++) { DECODE[i] = 52 + i - '0'; } DECODE['+'] = 62; DECODE['/'] = 63; // 對於 URL 安全的 Base64,可以新增以下賦值 DECODE['-'] = 62; DECODE['_'] = 63; }
3. 使用解碼錶進行解碼
在解碼過程中,Base64 編碼的字串被逐個字元讀取,每個字元透過解碼錶轉換為其對應的 6 位二進制值。
4. 處理填充
Base64 編碼可能以 =
字元結尾,表示原始資料在編碼時不足 3 個位元組。在解碼時,這些填充字元被忽略,不參與解碼過程。
5. 生成原始資料
將解碼得到的 6 位二進制值重新組合,轉換回原始的位元組序列。
原理解釋
字元到值的對映:解碼錶提供了從 Base64 字元到其對應的 6 位二進制值的快速對映。這種對映是透過查詢表實現的,其中每個可能的字元(128 個 ASCII 字元)都有一個與之對應的整數值。
忽略非法字元:解碼錶中的非法字元被賦予一個特殊值(如 -1),在解碼過程中,這些值被忽略或導致解碼失敗。
效能最佳化:使用查詢表可以快速地進行字元到值的轉換,避免了複雜的條件判斷或迴圈,從而提高了解碼的效率。
上示例程式碼
以下是如何使用解碼錶進行 Base64 解碼的示例程式碼:
public byte[] decode(String encoded) { char[] chars = encoded.toCharArray(); int[] decodeTable = getDecodeTable(); // 獲取初始化的解碼錶 ByteArrayOutputStream output = new ByteArrayOutputStream(); for (int i = 0; i < chars.length;) { if (chars[i] == '=') { // 處理填充 break; } int value = decodeTable[chars[i] & 0xFF]; if (value == -1) { // 非法字元 throw new IllegalArgumentException("Illegal character encountered"); } // 將 4 個 Base64 字元轉換為 3 個位元組 int threeBytes = (value << 18) | (decodeTable[chars[++i] & 0xFF] << 12) | (decodeTable[chars[++i] & 0xFF] << 6) | decodeTable[chars[++i] & 0xFF]; output.write((threeBytes >> 16) & 0xFF); if (chars[++i] != '=') { output.write((threeBytes >> 8) & 0xFF); } if (chars[++i] != '=') { output.write(threeBytes & 0xFF); } i++; // 跳過最後一個字元,如果它是填充字元 } return output.toByteArray(); }
以上程式碼演示如何使用 Base64 解碼錶將 Base64 編碼的字串解碼為原始位元組陣列。實際的 Base64.Decoder
類實現可能會包含更多的細節,例如處理不同編碼模式等。
5. 編碼模式(Encoding Mode)
在 Java 的 java.util.Base64
包中,編碼模式(Encoding Mode)決定了 Base64 編碼的行為,包括字符集的使用和是否新增換行符。以下是對編碼模式的原始碼實現過程步驟和原理的詳細分析:
1. 定義編碼模式
Java 的 Base64
類提供了兩種編碼模式:
BASE64
:標準的 Base64 編碼模式,使用A-Z
、a-z
、0-9
、+
和/
字元,並且可以在每 76 個字元後新增換行符。URL_SAFE
:URL 安全的 Base64 編碼模式,使用A-Z
、a-z
、0-9
、-
和_
字元,適用於 URL 和檔名,也不新增換行符。
2. 初始化編碼器
編碼器可以透過 Base64.getEncoder()
獲取,並根據需要選擇編碼模式。例如,使用 URL 安全模式可以透過 encoder = Base64.getUrlEncoder()
實現。
3. 配置編碼器
編碼器可以進一步配置以滿足特定的編碼需求,例如禁用換行符或填充字元。這些配置可以透過編碼器的方法鏈呼叫來完成。
Base64.Encoder encoder = Base64.getEncoder(); encoder = encoder.withoutPadding(); // 禁用填充
4. 編碼資料
使用配置好的編碼器對資料進行編碼。編碼過程會根據編碼模式使用不同的字符集,並根據配置決定是否新增換行符。
byte[] encodedData = encoder.encode(originalData);
5. 輸出編碼結果
編碼後的資料可以作為位元組陣列或轉換為字串進行輸出。
String encodedString = new String(encodedData, StandardCharsets.UTF_8);
原理解釋
字符集選擇:不同的編碼模式使用不同的字符集。標準模式使用
+
和/
,而 URL 安全模式使用-
和_
代替,以避免在 URL 中引起歧義。換行符處理:在標準 Base64 編碼中,爲了提高可讀性,每 76 個字元後可以新增一個換行符。在 URL 安全模式或當禁用換行符時,不新增換行符。
填充處理:當輸入資料不是 3 個位元組的倍數時,Base64 編碼會在結果的末尾新增一個或兩個
=
字元作為填充。透過配置編碼器,可以禁用這種自動填充。
上示例程式碼
以下是使用不同編碼模式進行 Base64 編碼的示例:
import java.util.Base64; public class Base64EncodingExample { public static void main(String[] args) { byte[] data = "Some data to encode".getBytes(StandardCharsets.UTF_8); // 使用標準 Base64 編碼模式 Base64.Encoder standardEncoder = Base64.getEncoder(); String standardEncoded = new String(standardEncoder.encode(data)); // 使用 URL 安全的 Base64 編碼模式 Base64.Encoder urlSafeEncoder = Base64.getUrlEncoder().withoutPadding(); String urlSafeEncoded = new String(urlSafeEncoder.encode(data)); System.out.println("Standard Encoded: " + standardEncoded); System.out.println("URL Safe Encoded: " + urlSafeEncoded); } }
在這個示例中,我們使用標準 Base64 編碼模式和 URL 安全 Base64 編碼模式對相同的資料進行編碼,並輸出編碼結果。
注意事項
編碼模式的選擇應根據資料的使用場景來決定。例如,當資料將被用於 URL 傳輸時,應使用 URL 安全模式。
配置編碼器時,應考慮到接收端的解碼能力,確保編碼和解碼使用相同的模式和配置。
禁用填充可能會影響某些協議或應用程式的相容性,因為它們可能期望 Base64 編碼的資料以
=
字元結束。
透過這種方式,Base64
類的編碼模式提供了靈活性,以適應不同的編碼需求和使用場景。
6. 行長度和填充
在 Java 的 java.util.Base64
包中,行長度(line length)和填充(padding)是 Base64 編碼過程中的兩個可選配置,它們影響編碼輸出的格式。以下是行長度和填充的原始碼實現過程步驟和原理分析:
1. 定義行長度和填充的預設行為
在 Base64 編碼中,可以設定每行的字元數(行長度),以及是否在編碼資料的末尾新增填充字元 =
。
行長度:預設情況下,每行包含 76 個字元,這是爲了確保編碼後的文字符合 MIME 的要求。
填充:如果編碼的資料不是 3 個位元組的倍數,編碼後的輸出會在末尾新增一個或兩個
=
字元作為填充。
2. 配置編碼器
編碼器可以透過呼叫 Base64.getEncoder()
獲取,並使用 withoutPadding()
方法來配置不使用填充。
Base64.Encoder encoder = Base64.getEncoder().withoutPadding();
3. 實現自定義行分隔
雖然 Java 的 Base64.Encoder
沒有直接提供設定行長度的方法,但你可以透過自定義編碼邏輯來實現。例如,你可以在編碼後的結果上手動插入換行符。
4. 編碼資料
使用配置好的編碼器對資料進行編碼。編碼過程會根據是否配置了無填充來決定是否在輸出末尾新增 =
字元。
byte[] encodedData = encoder.encode(originalData);
5. 手動處理行分隔
如果你需要自定義行長度,可以在編碼後的結果上手動新增換行符。這可以透過遍歷編碼後的位元組陣列並每隔一定數量的字元插入一個換行符來實現。
原理解釋
行長度:設定行長度的目的是在編碼後的文字中新增可讀性,使其更適合在文字環境中展示和編輯。每 76 個 Base64 字元後新增一個換行符是 Base64 編碼的常見約定。
填充:Base64 編碼要求每三個位元組的原始資料轉換為四個 Base64 字元。如果原始資料的位元組數不是 3 的倍數,編碼器會在結果末尾新增一個或兩個
=
字元來填充。這表明編碼的資料不是原始資料的完整表示。自定義行分隔:在某些情況下,你可能需要不同於預設行長度的編碼格式。透過在編碼後的字串中手動插入換行符,可以實現自定義行分隔。
示例程式碼
以下是如何使用 Base64.Encoder
進行編碼,並手動新增自定義行分隔的示例:
import java.util.Base64; import java.io.ByteArrayOutputStream; public class Base64CustomLineLength { public static void main(String[] args) { String original = "Some data to encode"; byte[] data = original.getBytes(StandardCharsets.UTF_8); ByteArrayOutputStream baos = new ByteArrayOutputStream(); Base64.Encoder encoder = Base64.getEncoder().withoutPadding(); try { encoder.encode(data, baos); } catch (IllegalArgumentException e) { e.printStackTrace(); } String encoded = baos.toString(); // 手動新增自定義行分隔,例如每 50 個字元 StringBuilder sb = new StringBuilder(); for (int i = 0; i < encoded.length(); i += 50) { sb.append(encoded, i, Math.min(i + 50, encoded.length())); if (i + 50 < encoded.length()) { sb.append("\n"); // 新增換行符 } } encoded = sb.toString(); System.out.println("Custom line length encoded: " + encoded); } }
在這個示例中,我們使用 Base64.Encoder
對資料進行編碼,並在編碼後的結果上手動新增了每 50 個字元的換行符,實現了自定義行長度的效果。
注意事項
自定義行分隔可能會影響編碼資料的解碼,確保解碼時也考慮到了行分隔的處理。
禁用填充可能會影響某些協議或應用程式的相容性,因為它們可能期望 Base64 編碼的資料以
=
字元結束。在手動新增行分隔時,確保換行符的使用符合目標格式的要求。
最後
以上就是 Base64的核心類庫的全部介紹,瞭解 Base64的用法和原理,向高手靠近一點點。