Java的String.length()和String.getBytes().length
1. 概述
當我們使用 Java 工作時,操作字串是基本技能之一。因此,理解字串相關的方法對於編寫高效且無錯誤的程式碼至關重要。
兩個常用的方法String.length()
和String.getBytes().length,
乍看之下似乎很相似,但它們的用途不同。
在本教程中,讓我們了解這兩種方法並探討它們的差異。此外,我們還將討論何時使用每一項。
2. 初看String.length()
和String.getBytes().length
顧名思義, String.length()
方法傳回 string 的長度。另一方面, String.getBytes()
從給定字串中取得具有預設編碼的位元組數組。然後, String.getBytes().length
報告數組的長度。
如果我們編寫一個測試,我們可能會看到它們傳回相同的值:
String s = "beautiful";
assertEquals(9, s.length());
assertEquals(9, s.getBytes().length);
在 Java 中處理字串時,是否能保證String.length()
和String.getBytes().length
始終產生相同的值?
接下來我們就來了解一下。
3. String.length()
和String.getBytes().length
可以傳回不同的值
目前 JVM 的預設字元編碼或字元集在決定String.getBytes().length
的結果中起著重要作用。如果我們不向String.getBytes(),
它將使用預設的編碼方案進行編碼。
我們可以使用Charset.defaultCharset().displayName()
方法檢查 Java 環境的預設編碼。例如,目前JVM的預設編碼是UTF-8:
System.out.println(Charset.defaultCharset().displayName());
//output: UTF-8
因此,接下來,我們再測試兩個字串,看看String.length()
和String.getBytes().length
是否仍傳回相同的值:
String de = "schöne";
assertEquals(6, de.length());
assertEquals(7, de.getBytes().length);
String cn = "美丽";
assertEquals(2, cn.length());
assertEquals(6, cn.getBytes().length);
如同上面的測試所示,我們首先使用德語中的“ beautiful
”( “schöne”
)進行測試,然後我們使用另一個字符串,即中文中的“ beautiful
”( “美丽”
)。事實證明, String.length()
和String.getBytes().length
在兩個測試中產生了不同的值。
接下來我們就來了解為什麼會出現這樣的情況。
4. 字元編碼
在了解為什麼String.length()
和String.getBytes().length
對字串“schöne”
和“美丽”
給出不同的值之前,讓我們先快速了解字元編碼的工作原理。
字元編碼方案有很多種,例如UTF-8和UTF-16。我們可以將這些編碼方案分為兩類:
- 變長編碼
- 定長編碼
我們不會太深入地研究字符編碼。然而,對這兩種編碼技術的一般理解對於理解為什麼String.getBytes().length
可以具有與String.length().
那麼,接下來我們透過例子來快速了解這兩種編碼類型。
4.1.固定長度編碼
固定長度編碼使用相同數量的位元組來對任何字元進行編碼。固定長度編碼的典型例子是UTF-32,它總是使用四個位元組來編碼一個字元。所以,這就是“beautiful”
是如何用 UTF-32 編碼的:
char byte1 byte2 byte3 byte4
b 0 0 0 98
e 0 0 0 101
a 0 0 0 97
u 0 0 0 117
...
l 0 0 0 108
因此,當使用 UTF-32 字元集呼叫String.getBytes()
時,產生的位元組數組的長度將始終是字串中字元數的四倍:
Charset UTF_32 = Charset.forName("UTF_32");
String en = "beautiful";
assertEquals(9, en.length());
assertEquals(9 * 4, en.getBytes(UTF_32).length);
String de = "schöne";
assertEquals(6, de.length());
assertEquals(6 * 4, de.getBytes(UTF_32).length);
String cn = "美丽";
assertEquals(2, cn.length());
assertEquals(2 * 4, cn.getBytes(UTF_32).length);
也就是說,如果JVM預設編碼設定為UTF-32,那麼String.length()
和String.getBytes().length
的結果總是不同的。
我們中的一些人可能會注意到,在儲存UTF-32 編碼字元時,即使某些字元(例如ASCII 字元)只需要一個字節,我們仍然分配四個字節,其中三個位元組用零填充。這有點低效。
因此,引入了變長字符編碼。
4.2.變長編碼
變長編碼使用不同數量的位元組來編碼不同的字元。 UTF-8 是我們的預設編碼。此外,它也是可變長度編碼方案的一個範例。那麼,我們來看看UTF-8是如何對字元進行編碼的。
UTF-8 使用一到四個位元組對字元進行編碼,具體取決於字元的代碼點。代碼點是字元的整數表示。例如, 'b'
的十進位代碼點為98
,十六進位代碼點為U+0062
,與其 ASCII 代碼相同。
接下來我們來看看UTF-8是如何決定一個字元編碼使用多少位元組的:
碼範圍 | 位元組數 |
---|---|
U+0000 至 U+007F | 1 |
U+0080 至 U+07FF | 2 |
U+0800 至 U+FFFF | 3 |
U+10000 至 U+10FFFF | 4 |
我們知道字元「b」的碼位是U+0062,它在上表第一行的範圍內。因此,UTF-8 僅使用一個位元組對其進行編碼。由於 U+0000 到 U+007F 是十進位的 0 到 127,因此UTF-8 使用一個位元組來編碼所有標準 ASCII 字元。這就是為什麼String.length()
和String.getBytes().length
對字串「 beautiful
」給出相同的結果 ( 9
)。
但是,如果我們檢查“ö”、“美”和“麗”的代碼點,我們會看到 UTF-8 使用不同的位元組數對它們進行編碼:
assertEquals("f6", Integer.toHexString('ö')); // U+00F6 -> 2 bytes
assertEquals("7f8e", Integer.toHexString('美')); // U+7F8E -> 3 bytes
assertEquals("4e3d", Integer.toHexString('丽')); // U+4E3D -> 3 bytes
因此, “schöne”.getBytes().length
返回 7 (5 + 2),而“美丽”.getBytes().length
則回傳 6 (3 + 3)。
5. 如何在String.length()
和String.getBytes().length
之間進行選擇
現在,我們清楚了String.length()
和String.getBytes().length
傳回相同值以及它們何時出現分歧的情況。那麼,可能會出現一個問題:我們什麼時候應該選擇每種方法?
在選擇這些方法時,我們應該考慮任務的脈絡:
-
String.length()
– 當我們處理字串的字元和邏輯內容並想要取得字串中的字元總數時,例如使用者輸入的最大長度驗證或字串中的字元移位 -
String.bytes().length
– 當我們處理面向位元組的操作並且需要知道字串**以位元組為單位的**大小時,例如讀取或寫入檔案或網路流
值得注意的是,當我們使用String.bytes(),
我們應該記住字元編碼起著重要的作用。 String.bytes()
使用預設的編碼方案對字串進行編碼。除此之外,我們也可以將所需的字元集傳遞給對字串進行編碼的方法,例如String.bytes(Charset.forName(“UTF_32”))
或String.bytes(StandardCharsets.UTF_16)
六,結論
在本文中,我們大致了解了字元編碼的工作原理,並探討了為什麼String.length()
和String.getBytes().length
會產生不同的結果。此外,我們也討論瞭如何在String.length()
和String.getBytes().length
之間進行選擇。
與往常一樣,範例的完整原始程式碼可在 GitHub 上取得。