Java 如何使用BOM编码/解码UTF-16LE字节数组?

Java 如何使用BOM编码/解码UTF-16LE字节数组?,java,unicode,utf-16,byte-order-mark,Java,Unicode,Utf 16,Byte Order Mark,我需要在java.lang.String之间对UTF-16字节数组进行编码/解码。字节数组是用a给我的,我需要用BOM表对字节数组进行编码 另外,因为我正在处理一个Microsoft客户机/服务器,所以我希望使用little endian(以及LE BOM)进行编码,以避免任何误解。我确实意识到,有了BOM,它应该可以大端运行,但我不想在Windows世界中逆流而上 例如,这里有一种方法,它将java.lang.String编码为UTF-16,并使用BOM以小尾端进行编码: public sta

我需要在
java.lang.String
之间对UTF-16字节数组进行编码/解码。字节数组是用a给我的,我需要用BOM表对字节数组进行编码

另外,因为我正在处理一个Microsoft客户机/服务器,所以我希望使用little endian(以及LE BOM)进行编码,以避免任何误解。我确实意识到,有了BOM,它应该可以大端运行,但我不想在Windows世界中逆流而上

例如,这里有一种方法,它将
java.lang.String
编码为
UTF-16
,并使用BOM以小尾端进行编码:

public static byte[] encodeString(String message) {

    byte[] tmp = null;
    try {
        tmp = message.getBytes("UTF-16LE");
    } catch(UnsupportedEncodingException e) {
        // should not possible
        AssertionError ae =
        new AssertionError("Could not encode UTF-16LE");
        ae.initCause(e);
        throw ae;
    }

    // use brute force method to add BOM
    byte[] utf16lemessage = new byte[2 + tmp.length];
    utf16lemessage[0] = (byte)0xFF;
    utf16lemessage[1] = (byte)0xFE;
    System.arraycopy(tmp, 0,
                     utf16lemessage, 2,
                     tmp.length);
    return utf16lemessage;
}
在Java中实现这一点的最佳方法是什么?理想情况下,我希望避免将整个字节数组复制到一个新的字节数组中,该数组在开始时分配了两个额外的字节

解码这样的字符串也是如此,但使用以下命令更简单:

“UTF-16”字符集名称将始终使用BOM进行编码,并将使用大/小尾端对数据进行解码,但“UnicodeBig”和“UnicodeLittle”对于以特定字节顺序进行编码非常有用。对于无BOM,请使用UTF-16LE或UTF-16BE-了解如何使用“\uFEFF”手动处理BOM。有关字符集字符串名称或(最好)类的规范命名,请参阅。还请注意,仅绝对需要支持

    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(string.length() * 2 + 2);
    byteArrayOutputStream.write(new byte[]{(byte)0xFF,(byte)0xFE});
    byteArrayOutputStream.write(string.getBytes("UTF-16LE"));
    return byteArrayOutputStream.toByteArray();
编辑:重新阅读您的问题,我发现您宁愿完全避免双数组分配。不幸的是,据我所知,API并没有提供这方面的信息。(有一个方法,但它已被弃用,您不能使用它指定编码)


在看到您的评论之前,我写了上述内容,我认为使用nio类的答案是正确的。我已经看过了,但是我对API还不太熟悉,无法直接了解如何实现这一点。

首先,对于解码,可以使用字符集“UTF-16”;自动检测初始BOM表的。对于UTF-16BE编码,您还可以使用“UTF-16”字符集-这将编写一个正确的BOM表,然后输出big-endian内容


对于使用BOM编码到little endian,我不认为您当前的代码太糟糕,即使使用了双重分配(除非您的字符串真的很可怕)。如果它们不是处理字节数组,而是处理java.nio ByteBuffer,并使用java.nio.charset.CharsetEncoder类,那么您可能需要做的是。(您可以从Charset.forName(“UTF-16LE”).newEncoder()中获得。)

这是在nio中实现的方法:

    return Charset.forName("UTF-16LE").encode(message)
            .put(0, (byte) 0xFF)
            .put(1, (byte) 0xFE)
            .array();

它当然应该更快,但我不知道它在封面下制作了多少个数组,但我对API的理解是,它应该尽量减少速度。

这是一个老问题,但我仍然找不到适合我的情况的可接受的答案。基本上,Java没有内置的带BOM的UTF-16LE编码器。因此,您必须推出自己的实现

以下是我最终得到的结果:

private byte[] encodeUTF16LEWithBOM(final String s) {
    ByteBuffer content = Charset.forName("UTF-16LE").encode(s);
    byte[] bom = { (byte) 0xff, (byte) 0xfe };
    return ByteBuffer.allocate(content.capacity() + bom.length).put(bom).put(content).array();
}

谢谢还有一个问题。。。使用“UTF-16”将数据编码为Big-Endian,我怀疑这不会与Microsoft数据很好地兼容(即使BOM表存在)。用Java用BOM编码UTF-16LE有什么方法吗?我会更新我的问题以反映我真正想要的…点击他给出的“查看此帖子”链接。基本上,在字符串的开头填充一个\uFEFF字符,然后编码为UTF-16LE,结果将有一个合适的BOM表。使用“UnicodeLittle”(假设您的JRE支持它-(“\uEFFF”+“我的字符串”)。否则使用getBytes(“UTF-16LE”)。虽然我会很惊讶,如果微软API期望BOM,但不能处理大端数据,他们倾向于使用BOM而不是其他平台。使用空字符串进行测试-如果没有数据,您可能会得到空数组。如果Microsoft定义一种格式,它希望UTF-16LE BOM开始一个文件,并且如果该文件以UTF-8 BOM或UTF-16BE BOM开始,它将不起作用,我完全不会感到惊讶。我完全不会感到惊讶,因为这正是我在excel加载CSV文件时观察到的行为——如果文件以UTF-16LE BOM开头,那么它会在UTF-16LE中加载数据,并在列之间使用制表符。任何其他字符序列,它加载列之间带有“,”或“;”(取决于语言环境!)的本地字符集中的数据。只需重申一下:“Unicodelitle”(也称为“x-UTF-16LE-BOM”)将把文件写入带有BOM的UTF-16 little-endian。这应该是编写文件的首选方法,但它似乎只有在Java6(JDK1.6)之后才可用。对于阅读,你应该坚持使用“UTF-16”。谢谢。此外,我希望这里不使用string.getBytes(“UTF-16LE”)分配整个字节数组——可能是将流包装为InputStream,这是我前面问题的重点:请注意,这段代码实际上分配了三次足够大的数组来容纳字符串,因为您有ByteArrayOutputStream的内部数组,该数组在call.toByteArray()中复制。一种方法是将ByteArrayOutputStream包装在OutputStreamWriter中,并将字符串写入其中,从而使其返回到只分配两个。然后,您仍然拥有ByteArrayOutputStream的内部状态和由.toByteArray()创建的副本,但没有.GetBytes的返回值。如果您这样做,似乎只是将字符数组替换为字节数组,因为OutputStreamWriter将委托给StreamEncoder类,该类将创建一个char[]缓冲区来检索字符串数据。字符串是不可变的,数组的大小是不变的,所以复制似乎是不可避免的。我认为nio应该帮助在ByteArrayoutPuttStream上进行双重创建,但这个实际上不起作用。put(0)和put(1)调用覆盖编码消息的ByteBuffer的前两个字节。
private byte[] encodeUTF16LEWithBOM(final String s) {
    ByteBuffer content = Charset.forName("UTF-16LE").encode(s);
    byte[] bom = { (byte) 0xff, (byte) 0xfe };
    return ByteBuffer.allocate(content.capacity() + bom.length).put(bom).put(content).array();
}