将byte []编码为String

时间:2013-11-10 20:23:49

标签: java encoding utf-8 character-encoding byte

Heyho,

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

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

或使用base64:

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

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

问题只是关于负面影响,如果可能的话,不是这样!

3 个答案:

答案 0 :(得分:16)

你应该绝对使用base64或者可能是hex。 (要么工作; base64更紧凑,但人类更难阅读。)

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

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

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

答案 1 :(得分:5)

你可以在String中存储一个字节,虽然这不是一个好主意。您不能使用UTF-8,因为这将管理字节,但更快更有效的方法是使用ISO-8859-1编码或普通的8位。最简单的方法是使用

String s1 = new String(data, 0);

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

来自UTF-8 on Wikipedia,正如Jon Skeet所说,这些编码在标准下无效。他们在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使用第二个选项与C兼容,将C视为终止符。

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

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

答案 2 :(得分: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;
}