Java實(shí)現(xiàn)ByteArray與String互轉(zhuǎn)的常見轉(zhuǎn)換方法
在現(xiàn)代 Java 開發(fā)中,處理字節(jié)數(shù)據(jù)(byte[])和字符串(String)之間的轉(zhuǎn)換是一項(xiàng)常見且至關(guān)重要的任務(wù)。無(wú)論是網(wǎng)絡(luò)通信、文件操作、加密解密還是序列化反序列化,我們都需要在 byte[] 和 String 之間進(jìn)行高效、準(zhǔn)確的轉(zhuǎn)換。然而,這個(gè)看似簡(jiǎn)單的操作背后,隱藏著一個(gè)容易被忽視但又極其關(guān)鍵的問(wèn)題——編碼(Encoding)。
什么是編碼?
編碼(Encoding)是指將一種數(shù)據(jù)形式(如字符)轉(zhuǎn)換為另一種數(shù)據(jù)形式(如字節(jié)序列)的過(guò)程。在計(jì)算機(jī)世界里,字符(如 ‘A’、‘中’、‘??’)本質(zhì)上是抽象的概念,而計(jì)算機(jī)只能處理二進(jìn)制數(shù)據(jù)。因此,我們需要一套規(guī)則(編碼標(biāo)準(zhǔn))來(lái)規(guī)定字符如何映射到字節(jié)序列上。
常見的字符編碼標(biāo)準(zhǔn)包括:
- ASCII:僅支持 128 個(gè)字符(0-127),主要用于英文。
- ISO-8859-1:也稱 Latin-1,支持 256 個(gè)字符,覆蓋了西歐語(yǔ)言。
- UTF-8:可變長(zhǎng)度編碼,支持世界上幾乎所有語(yǔ)言的字符,是互聯(lián)網(wǎng)上最廣泛使用的編碼。
- UTF-16:固定長(zhǎng)度或可變長(zhǎng)度(16位),常用于 Java 內(nèi)部表示字符串。
- GBK/GB2312:中文字符編碼,主要在中國(guó)大陸使用。
為什么編碼問(wèn)題如此重要?
想象一下,你從一個(gè)文件中讀取了一段文本數(shù)據(jù),這段數(shù)據(jù)是以 UTF-8 編碼保存的。如果你在處理這段數(shù)據(jù)時(shí),錯(cuò)誤地使用了 ISO-8859-1 編碼去解析它,那么你得到的可能就不是原始的中文文本,而是一串亂碼。這就是編碼問(wèn)題帶來(lái)的后果。
在 Java 中,String 類內(nèi)部使用的是 UTF-16 編碼。當(dāng)我們需要將 String 轉(zhuǎn)換為 byte[] 或者反過(guò)來(lái)時(shí),如果不指定正確的編碼方式,就很容易導(dǎo)致轉(zhuǎn)換失敗或產(chǎn)生亂碼。
常見的轉(zhuǎn)換方法與陷阱
1. 直接使用getBytes()和new String(byte[])
這是最容易想到的方法,但它存在很大的風(fēng)險(xiǎn)。
// 錯(cuò)誤示例:沒(méi)有指定編碼
String str = "Hello 世界 ??"; // 包含中文和 emoji
byte[] bytes = str.getBytes(); // 默認(rèn)使用平臺(tái)默認(rèn)編碼(通常是 ISO-8859-1 或類似)
String backToStr = new String(bytes); // 默認(rèn)使用平臺(tái)默認(rèn)編碼解碼
System.out.println("原字符串: " + str);
System.out.println("轉(zhuǎn)換后字符串: " + backToStr); // 可能出現(xiàn)亂碼!
這段代碼的問(wèn)題在于:
str.getBytes()沒(méi)有指定編碼,默認(rèn)使用 JVM 的默認(rèn)編碼(getDefaultCharset())。在不同操作系統(tǒng)或 JVM 設(shè)置下,這個(gè)默認(rèn)編碼可能不同。new String(bytes)也沒(méi)有指定編碼,同樣使用默認(rèn)編碼進(jìn)行解碼。如果編碼不一致,就會(huì)產(chǎn)生亂碼。
2. 明確指定編碼
解決這個(gè)問(wèn)題的關(guān)鍵是始終明確指定編碼。
import java.nio.charset.StandardCharsets;
import java.nio.charset.Charset;
import java.util.Arrays;
public class ByteToStringExample {
public static void main(String[] args) {
String originalStr = "Hello 世界 ??";
try {
// ? 正確做法:明確指定編碼
byte[] bytes = originalStr.getBytes(StandardCharsets.UTF_8); // 使用 UTF-8 編碼
String decodedStr = new String(bytes, StandardCharsets.UTF_8); // 使用 UTF-8 解碼
System.out.println("原始字符串: " + originalStr);
System.out.println("字節(jié)數(shù)組: " + Arrays.toString(bytes));
System.out.println("轉(zhuǎn)換回字符串: " + decodedStr);
System.out.println("是否相等: " + originalStr.equals(decodedStr));
// ?? 也可以使用 Charset 對(duì)象
Charset utf8Charset = StandardCharsets.UTF_8;
byte[] bytes2 = originalStr.getBytes(utf8Charset);
String decodedStr2 = new String(bytes2, utf8Charset);
System.out.println("使用 Charset 對(duì)象: " + decodedStr2);
} catch (Exception e) {
System.err.println("轉(zhuǎn)換過(guò)程中發(fā)生錯(cuò)誤: " + e.getMessage());
e.printStackTrace();
}
}
}
輸出示例:
原始字符串: Hello 世界 ?? 字節(jié)數(shù)組: [72, 101, 108, 108, 111, 32, -28, -67, -96, -27, -101, -120, 32, -25, -121, -101, -27, -101, -120] 轉(zhuǎn)換回字符串: Hello 世界 ?? 是否相等: true 使用 Charset 對(duì)象: Hello 世界 ??
3. 常用的編碼常量
Java 提供了 java.nio.charset.StandardCharsets 類,其中包含了常用的編碼常量,推薦優(yōu)先使用這些常量,因?yàn)樗鼈兪?static final 的,性能更好,也避免了字符串硬編碼的風(fēng)險(xiǎn)。
import java.nio.charset.StandardCharsets; // 推薦使用這些常量 byte[] utf8Bytes = "Hello".getBytes(StandardCharsets.UTF_8); byte[] asciiBytes = "Hello".getBytes(StandardCharsets.US_ASCII); byte[] latin1Bytes = "Hello".getBytes(StandardCharsets.ISO_8859_1); byte[] utf16Bytes = "Hello".getBytes(StandardCharsets.UTF_16); byte[] utf16BEBytes = "Hello".getBytes(StandardCharsets.UTF_16BE); byte[] utf16LEBytes = "Hello".getBytes(StandardCharsets.UTF_16LE);
4. 處理不支持的編碼
如果指定了一個(gè) JVM 不支持的編碼,會(huì)拋出 UnsupportedEncodingException(Java 8 及以前)或 IllegalCharsetNameException(Java 9+)。
import java.nio.charset.UnsupportedCharsetException;
import java.nio.charset.IllegalCharsetNameException;
public class EncodingErrorHandling {
public static void main(String[] args) {
String str = "Hello";
try {
// ? 這種編碼可能不存在
byte[] bytes = str.getBytes("NON_EXISTENT_ENCODING");
} catch (UnsupportedEncodingException | IllegalCharsetNameException e) {
System.err.println("編碼不支持: " + e.getMessage());
}
// ? 更安全的做法:先檢查編碼是否存在
try {
java.nio.charset.Charset charset = java.nio.charset.Charset.forName("UTF-8");
byte[] bytes = str.getBytes(charset);
System.out.println("成功使用 UTF-8 編碼: " + Arrays.toString(bytes));
} catch (Exception e) {
System.err.println("編碼錯(cuò)誤: " + e.getMessage());
}
}
}
實(shí)際應(yīng)用場(chǎng)景
1. 文件讀寫
當(dāng)你需要將字符串寫入文件或從文件讀取字符串時(shí),編碼尤為重要。
import java.io.*;
import java.nio.charset.StandardCharsets;
public class FileEncodingExample {
public static void main(String[] args) {
String filePath = "test.txt";
String content = "Hello 世界 ??\nThis is a test file.";
// ? 寫入文件(指定編碼)
try (OutputStreamWriter writer = new OutputStreamWriter(
new FileOutputStream(filePath), StandardCharsets.UTF_8)) {
writer.write(content);
System.out.println("文件寫入成功。");
} catch (IOException e) {
System.err.println("寫入文件失敗: " + e.getMessage());
}
// ? 讀取文件(指定編碼)
try (InputStreamReader reader = new InputStreamReader(
new FileInputStream(filePath), StandardCharsets.UTF_8)) {
StringBuilder sb = new StringBuilder();
int ch;
while ((ch = reader.read()) != -1) {
sb.append((char) ch);
}
String readContent = sb.toString();
System.out.println("讀取的文件內(nèi)容:\n" + readContent);
} catch (IOException e) {
System.err.println("讀取文件失敗: " + e.getMessage());
}
}
}
2. 網(wǎng)絡(luò)通信
在網(wǎng)絡(luò)傳輸中,數(shù)據(jù)通常以字節(jié)流的形式發(fā)送。服務(wù)器和客戶端必須使用相同的編碼來(lái)解析數(shù)據(jù)。
import java.net.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
public class NetworkEncodingExample {
public static void main(String[] args) {
// 模擬發(fā)送方:將字符串轉(zhuǎn)換為字節(jié)發(fā)送
String message = "Hello Server! 你好世界 ??";
byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);
// 模擬接收方:接收字節(jié)并轉(zhuǎn)換回字符串
try {
String receivedMessage = new String(messageBytes, StandardCharsets.UTF_8);
System.out.println("接收到的消息: " + receivedMessage);
} catch (Exception e) {
System.err.println("解碼失敗: " + e.getMessage());
}
}
}
3. 加密解密
在加密算法中,明文通常需要轉(zhuǎn)換為字節(jié)進(jìn)行加密,加密后的密文也是字節(jié),最后可能需要將解密后的字節(jié)轉(zhuǎn)換回字符串。
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class EncryptionDecryptionExample {
public static void main(String[] args) throws Exception {
String originalText = "Secret Message 世界 ??";
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128); // 128 位密鑰
SecretKey secretKey = keyGen.generateKey();
// 加密
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encryptedBytes = cipher.doFinal(originalText.getBytes(StandardCharsets.UTF_8));
String encodedEncrypted = Base64.getEncoder().encodeToString(encryptedBytes);
System.out.println("加密后的 Base64: " + encodedEncrypted);
// 解密
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encodedEncrypted));
String decryptedText = new String(decryptedBytes, StandardCharsets.UTF_8);
System.out.println("解密后的文本: " + decryptedText);
}
}
4. HTTP 請(qǐng)求響應(yīng)
在處理 HTTP 請(qǐng)求和響應(yīng)時(shí),請(qǐng)求體和響應(yīng)體的編碼也需要特別注意。
import java.net.http.*;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
public class HttpEncodingExample {
public static void main(String[] args) {
// 注意:這需要 Java 11+ 的 HttpClient API
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
// 發(fā)送 POST 請(qǐng)求
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/post"))
.header("Content-Type", "application/json; charset=UTF-8")
.POST(HttpRequest.BodyPublishers.ofString("{\"message\": \"Hello 世界 ??\"}", StandardCharsets.UTF_8))
.timeout(Duration.ofSeconds(20))
.build();
try {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
System.out.println("響應(yīng)狀態(tài)碼: " + response.statusCode());
System.out.println("響應(yīng)正文: " + response.body());
} catch (Exception e) {
System.err.println("HTTP 請(qǐng)求失敗: " + e.getMessage());
}
}
}
高級(jí)技巧與最佳實(shí)踐
1. 使用java.util.Base64進(jìn)行編碼轉(zhuǎn)換
有時(shí)候,我們需要將 byte[] 轉(zhuǎn)換為可讀的字符串(如 Base64),然后再轉(zhuǎn)換回來(lái)。
import java.util.Base64;
import java.nio.charset.StandardCharsets;
public class Base64Example {
public static void main(String[] args) {
String original = "Hello 世界 ??";
// ? 將字符串編碼為 Base64 字符串
byte[] bytes = original.getBytes(StandardCharsets.UTF_8);
String base64Encoded = Base64.getEncoder().encodeToString(bytes);
System.out.println("Base64 編碼: " + base64Encoded);
// ? 將 Base64 字符串解碼回原始字節(jié)
byte[] decodedBytes = Base64.getDecoder().decode(base64Encoded);
String decodedString = new String(decodedBytes, StandardCharsets.UTF_8);
System.out.println("解碼后: " + decodedString);
}
}
2. 自定義工具類進(jìn)行封裝
為了方便在項(xiàng)目中使用,可以創(chuàng)建一個(gè)工具類來(lái)封裝常用的轉(zhuǎn)換方法。
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
public class StringUtils {
/**
* 將字符串轉(zhuǎn)換為字節(jié)數(shù)組,使用指定編碼
* @param str 待轉(zhuǎn)換的字符串
* @param charset 編碼方式
* @return 字節(jié)數(shù)組
*/
public static byte[] stringToBytes(String str, Charset charset) {
if (str == null || charset == null) {
return new byte[0];
}
return str.getBytes(charset);
}
/**
* 將字節(jié)數(shù)組轉(zhuǎn)換為字符串,使用指定編碼
* @param bytes 待轉(zhuǎn)換的字節(jié)數(shù)組
* @param charset 編碼方式
* @return 字符串
*/
public static String bytesToString(byte[] bytes, Charset charset) {
if (bytes == null || charset == null) {
return "";
}
return new String(bytes, charset);
}
/**
* 將字符串轉(zhuǎn)換為 UTF-8 字節(jié)數(shù)組
* @param str 待轉(zhuǎn)換的字符串
* @return UTF-8 字節(jié)數(shù)組
*/
public static byte[] toUtf8Bytes(String str) {
return stringToBytes(str, StandardCharsets.UTF_8);
}
/**
* 將 UTF-8 字節(jié)數(shù)組轉(zhuǎn)換為字符串
* @param bytes 待轉(zhuǎn)換的字節(jié)數(shù)組
* @return 字符串
*/
public static String fromUtf8Bytes(byte[] bytes) {
return bytesToString(bytes, StandardCharsets.UTF_8);
}
// 測(cè)試方法
public static void main(String[] args) {
String testStr = "Hello 世界 ??";
byte[] utf8Bytes = toUtf8Bytes(testStr);
String backToStr = fromUtf8Bytes(utf8Bytes);
System.out.println("原始: " + testStr);
System.out.println("字節(jié): " + Arrays.toString(utf8Bytes));
System.out.println("還原: " + backToStr);
System.out.println("是否相等: " + testStr.equals(backToStr));
}
}
3. 處理特殊字符和 Emoji
現(xiàn)代應(yīng)用中經(jīng)常包含 Emoji 表情符號(hào)。這些字符通常需要使用 UTF-8 編碼才能正確處理。
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
public class EmojiExample {
public static void main(String[] args) {
String emojiStr = "Hello ????! How are you? ??";
// ? 使用 UTF-8 編碼
byte[] bytes = emojiStr.getBytes(StandardCharsets.UTF_8);
String decodedStr = new String(bytes, StandardCharsets.UTF_8);
System.out.println("原始字符串: " + emojiStr);
System.out.println("字節(jié)數(shù)組 (UTF-8): " + Arrays.toString(bytes));
System.out.println("解碼后: " + decodedStr);
System.out.println("是否相等: " + emojiStr.equals(decodedStr));
}
}
4. 性能考量
雖然 StandardCharsets 是推薦的,但在性能敏感的場(chǎng)景下,可以預(yù)先獲取并緩存 Charset 對(duì)象。
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
public class PerformanceExample {
// 預(yù)先緩存常用編碼
private static final Charset UTF8_CHARSET = StandardCharsets.UTF_8;
private static final Charset ASCII_CHARSET = StandardCharsets.US_ASCII;
public static void main(String[] args) {
String str = "Hello World!";
long startTime, endTime;
// 使用緩存的 Charset
startTime = System.nanoTime();
for (int i = 0; i < 1000000; i++) {
byte[] bytes = str.getBytes(UTF8_CHARSET);
String result = new String(bytes, UTF8_CHARSET);
}
endTime = System.nanoTime();
System.out.println("使用預(yù)緩存 Charset 耗時(shí): " + (endTime - startTime) / 1000000.0 + " ms");
// 使用 StandardCharsets 直接訪問(wèn)
startTime = System.nanoTime();
for (int i = 0; i < 1000000; i++) {
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
String result = new String(bytes, StandardCharsets.UTF_8);
}
endTime = System.nanoTime();
System.out.println("直接使用 StandardCharsets 耗時(shí): " + (endTime - startTime) / 1000000.0 + " ms");
}
}
常見錯(cuò)誤與解決方案
1. 編碼不匹配導(dǎo)致的亂碼
問(wèn)題:讀取文件時(shí)使用了錯(cuò)誤的編碼。
解決方案:始終明確指定編碼。
// ? 錯(cuò)誤做法 String content = new String(fileBytes); // 使用默認(rèn)編碼 // ? 正確做法 String content = new String(fileBytes, StandardCharsets.UTF_8);
2. 混合使用不同編碼
問(wèn)題:在同一個(gè)應(yīng)用中混合使用不同的編碼。
解決方案:在整個(gè)項(xiàng)目中統(tǒng)一使用一種編碼(通常是 UTF-8)。
3. 忽略異常處理
問(wèn)題:未處理 UnsupportedEncodingException。
解決方案:總是進(jìn)行異常處理。
try {
byte[] bytes = str.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
// 記錄日志或使用默認(rèn)編碼
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
}
總結(jié)
在 Java 中處理 byte[] 和 String 之間的轉(zhuǎn)換時(shí),編碼是一個(gè)核心且不容忽視的問(wèn)題。正確的做法是始終明確指定編碼方式,推薦使用 java.nio.charset.StandardCharsets 中提供的常量。通過(guò)合理的編碼選擇和處理,我們可以避免亂碼問(wèn)題,確保程序的穩(wěn)定性和可靠性。記住,編碼不僅僅是技術(shù)細(xì)節(jié),更是保證數(shù)據(jù)完整性和應(yīng)用健壯性的關(guān)鍵。
以上就是Java實(shí)現(xiàn)ByteArray與String互轉(zhuǎn)的常見轉(zhuǎn)換方法的詳細(xì)內(nèi)容,更多關(guān)于Java ByteArray與String互轉(zhuǎn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Springdoc替換swagger的實(shí)現(xiàn)步驟分解
最近在spring看到的,spring要對(duì)api文檔動(dòng)手了,有些人說(shuō)swagger不好用,其實(shí)也沒(méi)那么不好用,有人說(shuō)代碼還是有點(diǎn)侵入性,這倒是真的,我剛試了springdoc可以說(shuō)還是有侵入性但是也可以沒(méi)有侵入性,這就看你對(duì)文檔有什么要求了2023-02-02
java 中序列化與readResolve()方法的實(shí)例詳解
這篇文章主要介紹了java 中序列化與readResolve()方法的實(shí)例詳解的相關(guān)資料,這里提供實(shí)例幫助大家理解這部分知識(shí),需要的朋友可以參考下2017-08-08
MyBatisPlus 查詢selectOne方法實(shí)現(xiàn)
本文主要介紹了MyBatisPlus 查詢selectOne方法實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01
SpringBoot集成WebServlet出現(xiàn)自定義servlet請(qǐng)求失敗的問(wèn)題解決方案
SpringBoot中以Bean方式注冊(cè)Servlet時(shí)遇到的問(wèn)題,通過(guò)了解DispatcherServlet的原理,發(fā)現(xiàn)默認(rèn)路徑?jīng)_突是主要原因,本文介紹SpringBoot集成WebServlet出現(xiàn)自定義servlet請(qǐng)求失敗的問(wèn)題解決方案,感興趣的朋友一起看看吧2025-03-03
Java基礎(chǔ)之刪除文本文件中特定行的內(nèi)容
這篇文章主要介紹了Java基礎(chǔ)之刪除文本文件中特定行的內(nèi)容,文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)java基礎(chǔ)的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04
Java與JavaScript自動(dòng)化測(cè)試Selenium使用詳解
這篇文章主要介紹了Java與JavaScript自動(dòng)化測(cè)試Selenium的使用,Selenium是一個(gè)用于Web應(yīng)用程序測(cè)試的工具,Selenium測(cè)試直接運(yùn)行在瀏覽器中,就像真正的用戶在操作一樣,需要的朋友可以參考下2025-05-05
Java如何接收并解析HL7協(xié)議數(shù)據(jù)
文章主要介紹了HL7協(xié)議及其在醫(yī)療行業(yè)中的應(yīng)用,詳細(xì)描述了如何配置環(huán)境、接收和解析數(shù)據(jù),以及與前端進(jìn)行交互的實(shí)現(xiàn)方法,文章還分享了使用7Edit工具進(jìn)行調(diào)試的經(jīng)驗(yàn),并記錄了一個(gè)常見的解析問(wèn)題及其解決方法2024-12-12

