Java ByteBuffer 转换为字符串

161

以这种方式将 ByteBuffer 转换为 String,这是正确的方法吗?

String k = "abcd";
ByteBuffer b = ByteBuffer.wrap(k.getBytes());
String v = new String(b.array());

if(k.equals(v))
    System.out.println("it worked");
else
    System.out.println("did not work");

我问的原因是这看起来太简单了,而其他方法例如Java:将字符串转换为ByteBuffer及其相关问题看起来更加复杂。


3
好的,你试过了吗? - tckmn
6
是的,我做到了并且它有效。但我见过其他更复杂的实现,比如https://dev59.com/VXM_5IYBdhLWcg3wvFzI。 - vikky.rk
1
@Doorknob等。他缺少编码,当语法被纠正时,他的示例将运行,但他的方法仍然不正确。 - Gus
12个回答

168

有一种更简单的方法可以将ByteBuffer解码为String,并且没有任何问题,这是由安迪·托马斯提到的。

String s = StandardCharsets.UTF_8.decode(byteBuffer).toString();

6
请注意,UTF-8 可能不是将字节转换为字符串和反向转换的最佳字符集。对于字节到字符的一对一映射,最好使用 ISO-8859-1 字符集,参见 https://dev59.com/kWox5IYBdhLWcg3wmFaV。 - asmaier
2
此外,如果您不是真正需要一个字符串,那么CharBufferdecode()返回的是一个CharSequence(类似于String),因此您可以避免额外的复制并直接使用它。 - David Ehrmann
@DavidEhrmann CharBuffer没有decode方法 - 你在这里指的是什么? - Tom Anderson
@TomAnderson Charset类具有decode()方法。 - David Ehrmann
1
@DavidEhrmann 啊!我完全误解了你的评论,抱歉! - Tom Anderson

90

编辑(2018):@xinyongCheng 编写的兄弟答案更简单,应该成为被接受的答案。

如果您知道字节在平台的默认字符集中,那么您的方法是合理的。在您的示例中,这是正确的,因为k.getBytes()返回平台的默认字符集中的字节。

更频繁地,您需要指定编码。然而,有一种比您链接的问题更简单的方法。字符串API提供了在特定编码中将String和byte[]数组之间转换的方法。这些方法建议使用CharsetEncoder/CharsetDecoder “当需要更多地控制解码[编码]过程时”

要从特定编码的String中获取字节,可以使用兄弟getBytes()方法:

byte[] bytes = k.getBytes( StandardCharsets.UTF_8 );

要将特定编码的字节放入字符串中,可以使用不同的字符串构造函数:

String v = new String( bytes, StandardCharsets.UTF_8 );

请注意,ByteBuffer.array()是一个可选的操作。如果您使用数组构造了ByteBuffer,则可以直接使用该数组。否则,如果您想要安全地操作,请使用ByteBuffer.get(byte[] dst, int offset, int length)将字节从缓冲区获取到字节数组中。


ByteBuffer.get 函数中,输入又是一组字节,我该如何获取它?说再次使用 k.getbytes 没有任何意义,不是吗? - William Kinaan
@WilliamKinaan - 你有传递给ByteBuffer.get(byte[] dst, int offset, int length)的byte[]。你可以使用String()构造函数 String(byte[] bytes, int offset, int length, Charset charset)将其构建为字符串。你可以对这两个调用都使用相同的偏移和长度值。 - Andy Thomas
1
在java.nio.ByteBuffer中没有k.getBytes()方法(可能是我使用的版本没有)。因此,我使用了k.array()方法,它将返回byte[]。 - Madura Pradeep
@MaduraPradeep - 在问题和答案的示例代码中,k 是一个字符串,而不是 ByteBuffer。 - Andy Thomas
请注意,UTF-8 可能不是将字节转换为字符串和反之的最佳字符集。对于字节到字符的一对一映射,最好使用 ISO-8859-1,请参见 https://dev59.com/kWox5IYBdhLWcg3wmFaV - asmaier
这个解决方案还有一个问题,让我遇到了麻烦。ByteBuffer有一个arrayOffset()方法。通常,只有在对Buffer进行slice()操作时才会出现这种情况,但是Android却使用偏移量来分配缓冲区!这是一个非常难以发现的问题。 - Dustin

18

试试这个:

new String(bytebuffer.array(), "ASCII");

NB. 如果不知道byte数组的编码方式,就无法正确地将其转换为字符串。

希望这可以帮到你。


11
UTF-8 可能比 ASCII 更适合作为默认猜测编码? - Gus
3
根据 OP 使用 k.getBytes() 函数并使用默认字符集,因此不应该指定任何字符集。 - Andy Thomas
8
并非所有缓冲区都由数组支持,因此.array()可能会抛出异常。 - Dzmitry Lazerka
1
并非所有的字节缓冲都支持.array()方法。 - ScalaWilliam
3
注意!如果你使用 array() 函数,你必须同时使用 arrayOffset() 函数以正确开始数组中的位置。这是一个微妙的陷阱,因为通常情况下 arrayOffset() 的值是 0;但在极少数情况下,如果你不考虑它,就会出现难以发现的错误。 - oliver
显示剩余2条评论

15

只是想指出,假设ByteBuffer.array()总是有效的并不安全。

byte[] bytes;
if(buffer.hasArray()) {
    bytes = buffer.array();
} else {
    bytes = new byte[buffer.remaining()];
    buffer.get(bytes);
}
String v = new String(bytes, charset);

通常情况下,buffer.hasArray() 将根据您的使用情况始终为真或假。实际上,除非您真的希望它在任何情况下都能正常工作,否则安全起见,可以优化掉不需要的分支。但其他答案可能无法处理通过 ByteBuffer.allocateDirect() 创建的 ByteBuffer。


如果使用 ByteBuffer.wrap(bytes, offset, size) 工厂创建缓冲区,则 .array() 将返回整个 bytes 数组。最好使用xinyong Cheng建议的形式。 - Lev Kuznetsov
.decode() 在 Charset 上是更好的解决方案,同意。我觉得我的回答的上下文是有用的信息,但现在不那么重要了。 - Fuwjax
2
小心!如果你使用 array(),你必须同时使用 arrayOffset() 来从数组的正确位置开始!这是一个微妙的陷阱,因为通常 arrayOffset() 是0;但在那些罕见的情况下,如果你不考虑它,你将会遇到难以发现的错误。 - oliver

8

仅仅调用array()的答案并不完全正确:当缓冲区已经部分消费或者是引用数组的一部分(你可以在给定偏移量处ByteBuffer.wrap一个数组,而不是从开头开始),我们必须在计算中考虑到这一点。这是适用于所有情况的缓冲区的通用解决方案(不包括编码):

if (myByteBuffer.hasArray()) {
    return new String(myByteBuffer.array(),
        myByteBuffer.arrayOffset() + myByteBuffer.position(),
        myByteBuffer.remaining());
} else {
    final byte[] b = new byte[myByteBuffer.remaining()];
    myByteBuffer.duplicate().get(b);
    return new String(b);
}

关于编码的问题,请参考安迪·托马斯的回答。


2

这个问题的根源是如何将字节解码为字符串?

可以使用JAVA NIO CharSet来完成:

public final CharBuffer decode(ByteBuffer bb)

FileChannel channel = FileChannel.open(
  Paths.get("files/text-latin1.txt", StandardOpenOption.READ);
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer);

CharSet latin1 = StandardCharsets.ISO_8859_1;
CharBuffer latin1Buffer = latin1.decode(buffer);

String result = new String(latin1Buffer.array());
  • 首先,我们创建一个通道并将其读入缓冲区。
  • 然后,解码方法将Latin1缓冲区解码为字符缓冲区。
  • 然后,我们可以将结果放入字符串中。

你的代码无法从latin1解码为utf8。虽然你的代码是正确的,但是将CharBuffer命名为utf8Buffer有些误导,因为它没有编码。 - Björn Lindqvist

1
将字符串转换为字节缓冲区,然后使用Java从字节缓冲区转换回字符串:
import java.nio.charset.Charset;
import java.nio.*;

String babel = "obufscate thdé alphebat and yolo!!";
System.out.println(babel);
//Convert string to ByteBuffer:
ByteBuffer babb = Charset.forName("UTF-8").encode(babel);
try{
    //Convert ByteBuffer to String
    System.out.println(new String(babb.array(), "UTF-8"));
}
catch(Exception e){
    e.printStackTrace();
}

首先打印出裸字符串,然后将ByteBuffer转换为数组()进行打印:

obufscate thdé alphebat and yolo!!
obufscate thdé alphebat and yolo!!

这对我很有帮助,将字符串转换为原始字节可以帮助检查发生了什么:

String text = "こんにちは";
//convert utf8 text to a byte array
byte[] array = text.getBytes("UTF-8");
//convert the byte array back to a string as UTF-8
String s = new String(array, Charset.forName("UTF-8"));
System.out.println(s);
//forcing strings encoded as UTF-8 as an incorrect encoding like
//say ISO-8859-1 causes strange and undefined behavior
String sISO = new String(array, Charset.forName("ISO-8859-1"));
System.out.println(sISO);

打印您的字符串,解释为UTF-8,然后再解释为ISO-8859-1:
こんにちは
ããã«ã¡ã¯

0
这是我在一个java.nio.ByteBuffer实例上使用的唯一方法: String fileContent = new String(bb.array(), StandardCharsets.UTF_8); 以下是相关代码片段:
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.channels.FileChannel;
import java.nio.ByteBuffer;


Path path = Paths.get("/home/binita/testbb");
FileChannel fileChannel = FileChannel.open(path, 
                 EnumSet.of(StandardOpenOption.READ
                    )
                 );  
            
ByteBuffer bb = ByteBuffer.allocate(1024);
int bytesRead = fileChannel.read(bb);
if(bytesRead > 0) {
 String fileContent = new String(bb.array(), StandardCharsets.UTF_8);
}

0
请注意(除了编码问题外),与这里的许多答案示例不同,一些更复杂的链接代码会费心获得有关 ByteBuffer 的“活动”部分(例如通过使用 position 和 limit),而不仅仅是对整个后备数组中的所有字节进行编码。

0
private String convertFrom(String lines, String from, String to) {
    ByteBuffer bb = ByteBuffer.wrap(lines.getBytes());
    CharBuffer cb = Charset.forName(to).decode(bb);
    return new String(Charset.forName(from).encode(cb).array());
};
public Doit(){
    String concatenatedLines = convertFrom(concatenatedLines, "CP1252", "UTF-8");
};

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