ByteBuffer,CharBuffer,String和Charset

时间:2014-06-29 23:41:26

标签: java character-encoding bytebuffer

我试图根据字符集对字符,它们在字节序列中的表示进行排序,以及如何在Java中将一个字符集转换为另一个字符集。我有些困难。

例如,

ByteBuffer bybf = ByteBuffer.wrap("Olé".getBytes());

我的理解是:

  • 字符串始终以Java格式存储为UTF-16字节序列(每个字符2个字节,大端)
  • getBytes()结果是相同的UTF-16字节序列
  • wrap()维持此序列
  • bybf因此是字符串Olé
  • 的UTF-16大端表示

因此在此代码中:

Charset utf16 = Charset.forName("UTF-16");  
CharBuffer chbf = utf16.decode(bybf);  
System.out.println(chbf);  

decode()应该

  • bybf解释为UTF-16字符串表示
  • "转换"它到原始字符串Olé

实际上不应该更改任何字节,因为所有内容都是UTF-16存储的,而UTF-16 Charset应该是一种"中立运算符"。但结果打印为:

??

怎么可能?

其他问题:为了正确转换,似乎Charset.decode(ByteBuffer bb)要求bb是字符串的UTF-16大端字节序列图像。 这是正确的吗?


修改:从提供的答案中,我做了一些测试来打印ByteBuffer内容和通过解码获得的chars。字节[编码与= "Olé".getBytes(charsetName)]打印在第一行组上,其他行是通过用各种Charset#decode(ByteBuffer)解码字节[与Charset]获得的字符串。

我还确认在Windows 7计算机上将String存储到byte[]的默认编码是windows-1252(除非字符串包含需要UTF-8的字符)。

Default VM encoding: windows-1252  
Sample string: "Olé"  


  getBytes() no CS provided : 79 108 233  <-- default (windows-1252), 1 byte per char
     Decoded as windows-1252: Olé         <-- using the same CS than getBytes()
           Decoded as UTF-16: ??          <-- using another CS (doesn't work indeed)

  getBytes with windows-1252: 79 108 233  <-- same than getBytes()
     Decoded as windows-1252: Olé

         getBytes with UTF-8: 79 108 195 169  <-- 'é' in UTF-8 use 2 bytes
            Decoded as UTF-8: Olé

        getBytes with UTF-16: 254 255 0 79 0 108 0 233 <-- each char uses 2 bytes with UTF-16
           Decoded as UTF-16: Olé                          (254-255 is an encoding tag)

3 个答案:

答案 0 :(得分:7)

你大多是正确的。

java中的本机字符表示形式为UTF-16。但是,在将字符转换为字节时,您可以指定正在使用的字符集,或者系统使用它的默认值,每当我检查时,它通常为UTF-8。如果您正在混合和匹配,这将产生有趣的结果。

例如我的系统如下

System.out.println(Charset.defaultCharset().name());
ByteBuffer bybf = ByteBuffer.wrap("Olé".getBytes());
Charset utf16 = Charset.forName("UTF-16");
CharBuffer chbf = utf16.decode(bybf);
System.out.println(chbf);
bybf = ByteBuffer.wrap("Olé".getBytes(utf16));
chbf = utf16.decode(bybf);
System.out.println(chbf);

生成

UTF-8
佬쎩
奥莱

所以这部分只有在UTF-16是默认字符集时才是正确的 getBytes() result is this same UTF-16 byte sequence.

因此要么始终指定您正在使用的最安全的字符集,因为您将始终知道发生了什么,或者始终使用默认值。

答案 1 :(得分:7)

  

字符串始终以Java格式存储为UTF-16字节序列(每个字符2个字节,大端字节)

  

getBytes()结果是相同的UTF-16字节序列

没有。它将UTF-16字符编码到平台默认字符集中,无论是什么。弃用。

  

wrap()维护这个序列

wrap()维护一切。

  

bybf因此是字符串Olé

的UTF-16大端表示

没有。它包装了平台原始字符串的默认编码。

  

decode()应该

     
      
  • 将bybf解释为UTF-16字符串表示
  •   

不,见上文。

  
      
  • 将其“转换”为原始字符串Olé。
  •   

除非平台的默认编码是“UTF-16”。

答案 2 :(得分:0)

我在使用双字节字符集编码的数据时遇到了几乎相同的问题。 上面的答案3已经包含了你应该关注的重要缺陷。

  1. 为源编码定义Charset。
  2. 如果目标编码与本地系统编码不同,则仅为其定义Charset。
  3. 以下代码正常工作

    public static String convertUTF16ToString(byte[] doc)
    {
        final Charset doublebyte = StandardCharsets.UTF_16;
        // Don't need this because it is my local (system default).  
        //final Charset ansiCharset = StandardCharsets.ISO_8859_1;
    
        final CharBuffer encoded = doublebyte.decode(ByteBuffer.wrap(doc));
        StringBuffer sb = new StringBuffer(encoded);
        return sb.toString();        
    }
    

    用您喜欢的编码替换系统默认值。

    public static String convertUTF16ToUTF8(byte[] doc)
    {
        final Charset doublebyte = StandardCharsets.UTF_16; 
        final Charset utfCharset = StandardCharsets.UTF_8; 
        final Charset ansiCharset = StandardCharsets.ISO_8859_1;
    
        final CharBuffer encoded1 = doublebyte.decode(ByteBuffer.wrap(doc));
        StringBuffer sb = new StringBuffer(encoded1);
        final byte[] result = ansiCharset.encode(encoded1).array();
        // alternative to utf-8
        //final byte[] result = utfCharset.encode(encoded1).array();
    
        return new String(result);        
    }