在 Java 中建立可變字串
一、簡介
在本教程中,我們將討論在 Java 中建立可變String
的幾種方法。
2. 字串的不變性
與 C 或 C++ 等其他程式語言不同,Java 中的Strings
是不可變的。
Strings
的這種不可變性質也意味著對String
的任何修改都會在記憶體中建立具有修改內容的新String
,並傳回更新後的參考。 Java 提供了StringBuffer
和StringBuilder
等庫類別來有效地處理可變文字資料。
3. 使用反射的可變字串
我們可以嘗試使用反射框架在 Java 中建立可變String
。 Java 中的反射框架可讓我們在執行時間檢查和修改物件、方法及其屬性的結構。雖然它是一個非常強大的工具,但應謹慎使用,因為它可能會在沒有警告的情況下在程序中留下錯誤。
我們可以使用框架的一些方法來更新Strings,
從而建立一個可變物件。讓我們先建立兩個Strings
,一個作為String
文字,另一個使用new
關鍵字:
String myString = "Hello World";
String otherString = new String("Hello World");
現在,我們在String
類別上使用 Reflection 的getDeclaredField()
方法來取得Field
實例,並使其可供我們覆寫該值:
Field f = String.class.getDeclaredField("value");
f.setAccessible(true);
f.set(myString, "Hi World".toCharArray());
當我們將第一個字串的值設為其他值並嘗試列印第二個字串時,會出現突變值:
System.out.println(otherString);
Hi World
因此,我們改變了一個String,
任何引用這個文字的String
物件都會獲得其中“Hi World”
的更新值。這可能會在系統中引入錯誤並導致大量損壞。 Java 程式運行的基本假設是Strings
是不可變的。任何偏離這一點的行為都可能是災難性的。
還需要注意的是,上面的範例非常過時,並且不適用於較新的 Java 版本。
4. Charsets
和字串
4.1. Charsets
簡介
上述解決方案有很多缺點且不方便。改變字串的另一種方法是為我們的程式實作自訂CharSet
。
計算機只能透過數字代碼來理解人造字元。 Charset
是一個字典,用於維護字元與其二進位對應項的對應。例如, ASCII
字元集有 128 個字元。標準化的字元編碼格式以及定義的Charset,
可確保文字在全球數字系統中正確解釋。
Java 提供了對編碼和轉換的廣泛支援。其中包括 US-ASCII、ISO-8859-1、UTF-8 和 UTF-16 等。
4.2.使用Charset
讓我們來看看一個如何使用Charsets
對Strings
進行編碼和解碼的範例。我們將採用非 ASCII 字串,然後使用 UTF-8 字元集對其進行編碼。相反,我們將使用相同的字元集將字串解碼為原始輸入。
讓我們從輸入String
開始:
String inputString = "Hello, दुनिया";
我們使用java.nio.charset.Charset
的Charset.forName()
方法來取得 UTF-8 的字元集,並取得編碼器:
Charset charset = Charset.forName("UTF-8");
CharsetEncoder encoder = charset.newEncoder();
編碼器物件有一個encode()
方法,它需要一個CharBuffer
物件、一個ByteBuffer
物件和一個endOfInput
標誌。
CharBuffer
物件是保存Character
資料的緩衝區,可以透過以下方式取得:
CharBuffer charBuffer = CharBuffer.wrap(inputString);
ByteBuffer byteBuffer = ByteBuffer.allocate(64);
我們也建立一個大小為 64 的ByteBuffer
對象,然後將它們傳遞給encode()
方法以對輸入字串進行編碼:
encoder.encode(charBuffer, byteBuffer, true);
byteBuffer
物件現在儲存編碼的字元。我們可以解碼byteBuffer
物件的內容以再次顯示原始 String:
private static String decodeString(ByteBuffer byteBuffer) {
Charset charset = Charset.forName("UTF-8");
CharsetDecoder decoder = charset.newDecoder();
CharBuffer decodedCharBuffer = CharBuffer.allocate(50);
decoder.decode(byteBuffer, decodedCharBuffer, true);
decodedCharBuffer.flip();
return decodedCharBuffer.toString();
}
以下測試驗證我們是否能夠將字串解碼回其原始值:
String inputString = "hello दुनिया";
String result = ch.decodeString(ch.encodeString(inputString));
Assertions.assertEquals(inputString, result);
4.3.建立自訂Charset
我們也可以為我們的程式建立自訂Charset
類別定義。為此,我們必須提供以下方法的具體實作:
-
newDecoder()
– 這應該回傳一個CharsetDecoder
實例 -
newEncoder
() – 這應該回傳一個CharsetEncoder
實例
我們透過建立一個新的Charset
實例來開始內聯Charset
定義,如下所示:
private final Charset myCharset = new Charset("mycharset", null) {
// implement methods
}
我們已經看到Charsets
在字元的編碼和解碼生命週期中廣泛使用CharBuffer
物件。在我們的自訂字元集定義中,我們建立一個共用CharBuffer
物件以在整個程式中使用:
private final AtomicReference<CharBuffer> cbRef = new AtomicReference<>();
現在讓我們來寫newEncoder()
和newDecoder()
方法的簡單內聯實作來完成我們的Charset
定義。我們也會在方法中註入共享CharBuffer
物件cbRef
:
@Override
public CharsetDecoder newDecoder() {
return new CharsetDecoder(this, 1.0f, 1.0f) {
@Override
protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
cbRef.set(out);
while (in.remaining() > 0) {
out.append((char) in.get());
}
return CoderResult.UNDERFLOW;
}
};
}
@Override
public CharsetEncoder newEncoder() {
CharsetEncoder cd = new CharsetEncoder(this, 1.0f, 1.0f) {
@Override
protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) {
while (in.hasRemaining()) {
if (!out.hasRemaining()) {
return CoderResult.OVERFLOW;
}
char currentChar = in.get();
if (currentChar > 127) {
return CoderResult.unmappableForLength(1);
}
out.put((byte) currentChar);
}
return CoderResult.UNDERFLOW;
}
};
return cd;
}
4.4.使用自訂Charset
改變String
現在我們已經完成了Charset
定義,我們可以在我們的程式中使用這個字元集。請注意,我們有一個共享CharBuffer
實例,它在解碼過程中使用輸出CharBuffer
進行更新。這是改變字串的重要一步。
Java 中的String
類別提供了多個建構函式來建立和初始化String,
其中一個建構函式接受一個bytes
組陣列和一個Charset
:
public String(byte[] bytes, Charset charset) {
this(bytes, 0, bytes.length, charset);
}
我們使用此建構函式建立一個String,
並將自訂字元集物件myCharset
傳遞給它:
public String createModifiableString(String s) {
return new String(s.getBytes(), charset);
}
現在我們有了String
讓我們試著利用我們擁有的CharBuffer
來改變它:
public void modifyString() {
CharBuffer cb = cbRef.get();
cb.position(0);
cb.put("something");
}
在這裡,我們將CharBuffer's
0th
位置的內容更新為不同的值。由於此字元緩衝區是共享的,且字元集在解碼器的decodeLoop()
方法中維護對其的引用,因此底層的char[]
也發生了變更。我們可以透過新增測試來驗證這一點:
String s = createModifiableString("Hello");
Assert.assertEquals("Hello", s);
modifyString();
Assert.assertEquals("something", s);
5. 關於字串突變的最終想法
我們已經看到了幾種改變String
方法。 String
突變在 Java 世界中是有爭議的,主要是因為 Java 中幾乎所有程式都假定Strings
的非突變性質。
然而,我們需要多次更改Strings
,這就是為什麼 Java 為我們提供了StringBuffer
和StringBuilder
類別。這些類別使用可變的Characters
序列,因此很容易修改。使用這些類別是處理可變字元序列的最佳且最有效的方法。
六,結論
在本文中,我們研究了可變Strings
以及改變String
的方法。我們也了解使用簡單的演算法來改變String
缺點和困難。
與往常一樣,本文的程式碼可在 GitHub 上取得。