1. 概述
在数据传输中,我们经常需要处理字节数据。如果数据是编码过的字符串而非二进制,我们通常会使用Unicode进行编码。Unicode Transformation Format-8(UTF-8)是一种变长编码方式,能够编码所有可能的Unicode字符。
在这个教程中,我们将探讨UTF-8编码的字节与字符串之间的转换,并深入研究在Java中对字节数据进行UTF-8验证的关键方面。
2. UTF-8转换
在进入验证部分之前,让我们先回顾如何将字符串转换为UTF-8编码的字节数组,反之亦然:Java中如何将字符串转换为UTF-8编码的字节数组。
我们可以简单地通过目标编码调用getBytes()
方法将字符串转换为字节数组:
String UTF8_STRING = "Hello 你好";
byte[] UTF8_BYTES = UTF8_STRING.getBytes(StandardCharsets.UTF_8);
相反,String
类提供了一个构造函数,通过一个字节数组和其源编码创建一个String
实例:
String decodedStr = new String(array, StandardCharsets.UTF_8);
我们使用的构造函数对解码过程控制较少。当字节数组包含无法映射的字符序列时,它会用默认替换字符 �
替换它们:
@Test
void whenDecodeInvalidBytes_thenReturnReplacementChars() {
byte[] invalidUtf8Bytes = {(byte) 0xF0, (byte) 0xC1, (byte) 0x8C, (byte) 0xBC, (byte) 0xD1};
String decodedStr = invalidUtf8Bytes.getBytes(StandardCharsets.UTF_8);
assertEquals("�����", decodedStr);
}
因此,我们不能使用此方法来验证字节数组是否采用UTF-8编码。
3. 字节数组验证
Java提供了使用CharsetDecoder
简单验证字节数组是否为UTF-8编码的方法:
CharsetDecoder charsetDecoder = StandardCharsets.UTF_8.newDecoder();
CharBuffer decodedCharBuffer = charsetDecoder.decode(java.nio.ByteBuffer.wrap(UTF8_BYTES));
如果解码过程成功,我们就认为这些字节是有效的UTF-8编码。否则,decode()
方法会抛出MalformedInputException:
:
@Test
void whenDecodeInvalidUTF8Bytes_thenThrowsMalformedInputException() {
CharsetDecoder charsetDecoder = StandardCharsets.UTF_8.newDecoder();
assertThrows(MalformedInputException.class,() -> {
charsetDecoder.decode(java.nio.ByteBuffer.wrap(INVALID_UTF8_BYTES));
});
}
4. 字节流验证
当我们源数据是字节流而不是字节数组时,我们可以读取InputStream
并将内容放入一个字节数组。随后,我们可以对字节数组进行编码验证。
然而,我们的首选是直接验证InputStream
。这可以避免创建额外的字节数组,减少应用程序的内存占用。特别是当我们处理大流时这一点尤为重要。
在本节中,我们将定义以下常量作为源UTF-8编码的InputStream
:
InputStream UTF8_INPUTSTREAM = new ByteArrayInputStream(UTF8_BYTES);
4.1. 使用Apache Tika验证
Apache Tika是一个开源的内容分析库,提供了一套类,用于检测和从不同文件格式提取文本内容。
我们需要在pom.xml
中添加以下Apache Tika的核心和标准解析器依赖:
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
<version>2.9.1</version>
</dependency>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-parsers-standard-package</artifactId>
<version>2.9.1</version>
</dependency>
在Apache Tika中进行UTF-8验证时,我们实例化一个UniversalEncodingDetector
,并使用它检测InputStream
的编码。检测器返回一个Charset
实例。我们只需检查Charset
实例是否为UTF-8即可:
@Test
void whenDetectEncoding_thenReturnsUtf8() {
EncodingDetector encodingDetector = new UniversalEncodingDetector();
Charset detectedCharset = encodingDetector.detect(UTF8_INPUTSTREAM, new Metadata());
assertEquals(StandardCharsets.UTF_8, detectedCharset);
}
值得注意的是,当检测到只包含ASCII码前128个字符的流时,detect()
方法会返回ISO-8859-1,而不是UTF-8。
ISO-8859-1是一种单字节编码,用于表示ASCII字符,这些字符与前128个Unicode字符相同。由于这个特性,即使方法返回ISO-8859-1,我们仍然认为数据是UTF-8编码的。
4.2. 使用ICU4J验证
ICU4J代表Java的Unicode和全球化组件,由IBM发布。它为软件应用提供Unicode和全球化支持。我们需要在pom.xml
中添加以下ICU4J依赖:
<dependency>
<groupId>com.ibm.icu</groupId>
<artifactId>icu4j</artifactId>
<version>74.1</version>
</dependency>
在ICU4J中,我们创建一个CharsetDetector
实例来检测InputStream
的字符集。 类似于使用Apache Tika的验证,我们检查字符集是否为UTF-8:
@Test
void whenDetectEncoding_thenReturnsUtf8() {
CharsetDetector detector = new CharsetDetector();
detector.setText(UTF8_INPUTSTREAM);
CharsetMatch charsetMatch = detector.detect();
assertEquals(StandardCharsets.UTF_8.name(), charsetMatch.getName());
}
当ICU4J检测流的编码时,如果数据仅包含ASCII码的前128个字符,它也会表现出同样的行为,此时检测结果为ISO-8859-1。
5. 总结
在这篇文章中,我们探讨了UTF-8编码的字节和字符串转换,以及基于字节和流的不同类型的UTF-8验证。这次旅程为我们提供了实践代码,帮助我们更深入理解Java应用中的UTF-8。
如往常一样,示例代码可以在GitHub上找到。