在 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 上取得。