10

嘿嘿,

我想将可以是任何东西的字节数据转换为字符串。我的问题是,使用 UTF-8 对字节数据进行编码是否“安全”,例如:

String s1 = new String(data, "UTF-8");

或使用 base64:

String s2 = Base64.encodeToString(data, false); //migbase64

我只是担心使用第一种方法会产生负面影响。我的意思是两种变体都可以工作 p̶e̶r̶f̶e̶c̶t̶l̶y̶ ,但是s1可以包含 UTF-8 字符集的任何字符,s2只使用“可读”字符。我只是不确定是否真的需要使用base64。基本上我只需要创建一个字符串通过网络发送它并再次接收它。(在我的情况下没有其他方法:/)

问题只是关于负面影响,而不是可能的话!

4

3 回答 3

23

绝对应该使用 base64 或可能的十六进制。(任何一个都可以;base64 更紧凑,但人类更难阅读。)

您声称“两种变体都可以完美运行”,但事实并非如此。如果您使用第一种方法并且data实际上不是有效的 UTF-8 序列,则会丢失数据。您不是在尝试将 UTF-8 编码的文本转换为String,因此不要编写尝试这样做的代码。

用作编码将ISO-8859-1保留所有数据 - 但在很多情况下,返回的字符串不会轻易地跨其他协议传输。例如,它很可能包含不可打印的控制字符。

String(byte[], String)当您拥有固有的文本数据时才使用构造函数,而这些数据恰好是编码形式(其中编码被指定为第二个参数)。对于其他任何东西——例如音乐、视频、图像、加密或压缩数据——你应该使用一种将传入数据视为“任意二进制数据”并找到它的文本编码的方法......这正是 base64和十六进制做。

于 2013-11-10T20:26:03.193 回答
6

您可以在字符串中存储一个字节,但这不是一个好主意。您不能使用 UTF-8,因为这会管理字节,但更快、更有效的方法是使用 ISO-8859-1 编码或纯 8 位。最简单的方法是使用

String s1 = new String(data, 0);

或者

String s1 = new String(data, "ISO-8859-1");

正如 Jon Skeet 所指出的,来自Wikipedia 上的 UTF-8,这些编码在标准下无效。它们在 Java 中的行为各不相同。DataInputStream 将它们视为前三个版本相同,接下来的两个抛出异常。Charset 解码器默默地将它们视为单独的字符。

00000000 is \0
11000000 10000000 is \0
11100000 10000000 10000000 is \0
11110000 10000000 10000000 10000000 is \0
11111000 10000000 10000000 10000000 10000000 is \0
11111100 10000000 10000000 10000000 10000000 10000000 is \0

这意味着如果您在 String 中看到 \0,则无法确定原始 byte[] 值是什么。DataOutputStream 使用第二个选项来与将 \0 视为终止符的 C 兼容。

BTW DataOutputStream 不知道代码点,因此以 UTF-16 和 UTF-8 编码写入高代码点字符。

0xFE 和 0xFF 无效地出现在一个字符中。值 0x11000000+ 只能出现在字符的开头,不能出现在多字节字符内。

于 2013-11-10T20:28:12.400 回答
3

用 Java 确认接受的答案。重复一遍,UTF-8、UTF-16 不会保留所有字节值。ISO-8859-1 确实保留了所有字节值。但如果要在 JVM 之外传输编码字节,请使用 Base64。

@Test
public void testBase64() {
    final byte[] original = enumerate();
    final String encoded = Base64.encodeBase64String( original );
    final byte[] decoded = Base64.decodeBase64( encoded );
    assertTrue( "Base64 preserves bytes", Arrays.equals( original, decoded ) );
}

@Test
public void testIso8859() {
    final byte[] original = enumerate();
    String s = new String( original, StandardCharsets.ISO_8859_1 );
    final byte[] decoded = s.getBytes( StandardCharsets.ISO_8859_1 );
    assertTrue( "ISO-8859-1 preserves bytes", Arrays.equals( original, decoded ) );
}

@Test
public void testUtf16() {
    final byte[] original = enumerate();
    String s = new String( original, StandardCharsets.UTF_16 );
    final byte[] decoded = s.getBytes( StandardCharsets.UTF_16 );
    assertFalse( "UTF-16 does not preserve bytes", Arrays.equals( original, decoded ) );
}

@Test
public void testUtf8() {
    final byte[] original = enumerate();
    String s = new String( original, StandardCharsets.UTF_8 );
    final byte[] decoded = s.getBytes( StandardCharsets.UTF_8 );
    assertFalse( "UTF-8 does not preserve bytes", Arrays.equals( original, decoded ) );
}

@Test
public void testEnumerate() {
    final Set<Byte> byteSet = new HashSet<>();
    final byte[] bytes = enumerate();
    for ( byte b : bytes ) {
        byteSet.add( b );
    }
    assertEquals( "Expecting 256 distinct values of byte.", 256, byteSet.size() );
}

/**
 * Enumerates all the byte values.
 */
private byte[] enumerate() {
    final int length = Byte.MAX_VALUE - Byte.MIN_VALUE + 1;
    final byte[] bytes = new byte[length];
    for ( int i = 0; i < length; i++ ) {
        bytes[i] = (byte)(i + Byte.MIN_VALUE);
    }
    return bytes;
}
于 2015-11-19T18:15:02.077 回答