如何将字节数组转换为字符串,以及如何反向进行转换?

345

我需要在Android中将一个字节数组转换为字符串,但是我的字节数组包含负值。

如果我再将该字符串转换回字节数组,则得到的值与原始字节数组的值不同。

我该怎么做才能得到正确的转换?我使用的转换代码如下:

// Code to convert byte arr to str:
byte[] by_original = {0,1,-2,3,-4,-5,6};
String str1 = new String(by_original);
System.out.println("str1 >> "+str1);

// Code to convert str to byte arr:
byte[] by_new = str1.getBytes();
for(int i=0;i<by_new.length;i++) 
System.out.println("by1["+i+"] >> "+str1);

我被困在这个问题中。


5
为什么你要首先尝试将任意二进制数据转换为字符串?除了答案中已经提到的所有字符集问题之外,如果这样做,你还会滥用字符串(String)。使用一个 byte[] 存储二进制数据,使用字符串(String)存储文本会更好,这样做有什么不对吗? - Joachim Sauer
13
有时候你会有一些外部工具可以存储字符串。在这种情况下,你希望能够将一个字节数组转换成一个以某种方式编码的字符串。 - James Moore
27个回答

510

你的字节数组一定有一些编码方式。如果存在负值,那么该编码方式不能是ASCII。一旦弄清楚了这一点,就可以使用以下方法将一组字节转换为字符串:

byte[] bytes = {...}
String str = new String(bytes, StandardCharsets.UTF_8); // for UTF-8 encoding

你可以使用一堆编码方式,查看Oracle javadocs中的支持编码。


4
@MauricePerry 你能解释一下为什么它不能使用UTF-8吗?我需要翻译这句话。 - Asif Mushtaq
14
由于UTF-8将一些字符编码为2个或3个字节的字符串,因此并非每个字节数组都是有效的UTF-8编码字符串。ISO-8859-1可能是更好的选择:在这里,每个字符都被编码为一个字节。 - Maurice Perry
2
这可能有效,但是你应该尽量避免使用String构造函数。 - hfontanez
将一个字节映射到一个字符(使用8859-1),并且不进行异常处理(使用nio.charset):String str = new String(bytes, java.nio.charset.StandardCharsets.ISO_8859_1); - iman
4
自Java 1.7起,您可以使用新的String(bytes, StandardCharsets.UTF_8)方法。 - ihebiheb
显示剩余3条评论

120

byte[]String之间进行“适当的转换”需要明确指定所需使用的编码方式。如果你有一个byte[],但实际上它不包含文本数据,则不存在“适当的转换”方式。String用于文本,byte[]用于二进制数据,除非绝对必须这样做,否则唯一真正合理的事情是避免在它们之间进行转换。

如果你真的必须使用String来存储二进制数据,那么最安全的方法是使用Base64编码。


2
是的,字符编码是你必须了解的内容,以便在字符串和字节之间进行转换。 - Raedwald
3
Base64编码解决了我的问题。UTF-8对于所有输入都不起作用。 - Al-Alamin

45

根本问题是(我认为)您无意中使用了一个字符集,其中:

 bytes != encode(decode(bytes))

在某些情况下,UTF-8是这种字符集的一个例子。具体来说,某些字节序列在UTF-8中不是有效编码。如果UTF-8解码器遇到这些序列之一,则可能会丢弃有问题的字节或将其解码为“没有此类字符”的Unicode代码点。当您尝试将字符编码为字节时,结果将不同。
解决方案如下:
  1. 明确指定您正在使用的字符编码;即使用带有显式字符集的String构造函数和String.toByteArray方法。
  2. 为您的字节数据使用正确的字符集...或者另一个(例如“ Latin-1”),其中所有字节序列都映射到有效的Unicode字符。
  3. 如果您的字节是(真正的)二进制数据,并且您想能够通过“基于文本”的通道传输/接收它们,请使用诸如Base64编码之类的内容... 专门为此目的而设计

对于Java来说,最常见的字符集在java.nio.charset.StandardCharsets中。如果您要编码一个可以包含任何Unicode字符值的字符串,则建议使用UTF-8编码(UTF_8)

如果您想在Java中进行1:1映射,则可以使用ISO Latin Alphabet No. 1 - 更常称为"Latin 1"或简称为"Latin" (ISO_8859_1)。请注意,Java中的Latin-1是Latin-1的IANA版本,将字符分配给所有可能的256个值,包括control blocks C0 and C1。这些不可打印:您不会在任何输出中看到它们。

从Java 8开始,Java包含用于Base64编码/解码的java.util.Base64。对于URL安全编码,您可能希望使用Base64.getUrlEncoder而不是标准编码器。自Android Oreo(8),API级别26以来,此类也存在于Android中。

34

我们只需要使用数组构造一个新的 String: http://www.mkyong.com/java/how-do-convert-byte-array-to-string-in-java/

String s = new String(bytes);

根据使用的字符集,结果字符串的字节会有所不同。当您调用String#getBytes()时,使用new String(bytes),new String(bytes,Charset.forName(“utf-8”))和new String(bytes,Charset.forName(“utf-16”))将具有不同的字节数组(取决于默认字符集)。


10
不。产生的字符串字节数取决于您使用的字符集。当您调用String#getBytes()时,使用new String(bytes)new String(bytes, Charset.forName("utf-8"))new String(bytes, Charset.forName("utf-16"))会产生不同的字节数组(取决于默认字符集)。 - dutoitns
1
具有误导性。当以不同方式解码bytes时,生成的String中的char(因此显示的文本)会有所不同。使用默认编码进行字节转换(使用String#getBytes(“charset”)指定其他方式)必然会有所不同,因为它将转换不同的输入。字符串不存储它们生成的byte []char没有编码,而String也不以其他方式存储它。 - zapl

16
使用new String(byOriginal)将字节数组转换为字符串,再使用getBytes()方法将其转换回byte[]并不能保证生成的两个byte[]具有相同的值。这是由于调用了StringCoding.encode(..)方法,该方法会将String编码为Charset.defaultCharset()格式。在此编码过程中,编码器可能会选择替换未知字符并进行其他更改。因此,使用String.getBytes()方法可能不会返回与您最初传递给构造函数的字节数组相等的数组。

11

为什么会有这个问题:正如某人已经指出的那样: 如果您从一个byte[]开始,而它实际上不包含文本数据,则没有"适当的转换"。字符串用于文本,byte []用于二进制数据,除非绝对必须转换它们,否则唯一真正明智的做法就是避免在它们之间进行转换。

当我尝试从pdf文件创建byte[],然后将其转换为String,再将String作为输入并转换回文件时,我观察到了这个问题。

因此,请确保您的编码和解码逻辑与我相同。我明确地将byte[]编码为Base64,然后解码它以再次创建文件。

使用情况:由于某些限制,我正在尝试在请求(POST)中发送byte[],过程如下:

PDF文件 >> Base64.encodeBase64(byte[]) >> 字符串 >> 发送请求(POST) >> 接收字符串 >> Base64.decodeBase64(byte[]) >> 创建二进制文件

尝试这个,对我有效。

File file = new File("filePath");

        byte[] byteArray = new byte[(int) file.length()];

        try {
            FileInputStream fileInputStream = new FileInputStream(file);
            fileInputStream.read(byteArray);

            String byteArrayStr= new String(Base64.encodeBase64(byteArray));

            FileOutputStream fos = new FileOutputStream("newFilePath");
            fos.write(Base64.decodeBase64(byteArrayStr.getBytes()));
            fos.close();
        } 
        catch (FileNotFoundException e) {
            System.out.println("File Not Found.");
            e.printStackTrace();
        }
        catch (IOException e1) {
            System.out.println("Error Reading The File.");
            e1.printStackTrace();
        }

2
这个程序是否使用了外部库,例如Apache codec?如果是,请在回答中指明。 - Maarten Bodewes

7
即使

new String(bytes, "UTF-8")

如果正确,它会抛出UnsupportedEncodingException异常,这将强制您处理已检查的异常。自Java 1.6以来,您可以使用另一个构造函数将字节数组转换为字符串作为替代方法:
new String(bytes, StandardCharsets.UTF_8)

这个不会抛出任何异常。

转换回来也应该使用 StandardCharsets.UTF_8

"test".getBytes(StandardCharsets.UTF_8)

再次避免处理已检查的异常。


2
这是一个很好的评论,但使用new String本身就是不好的,所以它并不能解决潜在的问题。 - Maarten Bodewes

6
private static String toHexadecimal(byte[] digest){
        String hash = "";
    for(byte aux : digest) {
        int b = aux & 0xff;
        if (Integer.toHexString(b).length() == 1) hash += "0";
        hash += Integer.toHexString(b);
    }
    return hash;
}

1
这并没有回答问题。 - james.garriss
虽然没有回答问题,但很有用 +1 - Lazy Ninja

5
以下是样例代码,可以安全地将字节数组转换为字符串,并将字符串转换回字节数组。
 byte bytesArray[] = { 1, -2, 4, -5, 10};
 String encoded = java.util.Base64.getEncoder().encodeToString(bytesArray);
 byte[] decoded = java.util.Base64.getDecoder().decode(encoded);
 System.out.println("input: "+Arrays.toString(bytesArray));
 System.out.println("encoded: "+encoded);
 System.out.println("decoded: "+Arrays.toString(decoded));

输出:

input: [1, -2, 4, -5, 10]
encoded: Af4E+wo=
decoded: [1, -2, 4, -5, 10]

4

对我来说这个很好用:

String cd = "Holding some value";

将字符串转换为byte[]:

byte[] cookie = new sun.misc.BASE64Decoder().decodeBuffer(cd);

将byte[]转换为字符串:

cd = new sun.misc.BASE64Encoder().encode(cookie);

永远不要使用 sun. 内部类。自 Java 1.0 以来的每个教程都会警告你不要这样做,而新的模块化系统甚至默认禁止它。 - Maarten Bodewes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接