Как мне кодировать / декодировать байтовые массивы UTF-16LE с помощью спецификации? - PullRequest
21 голосов
/ 18 мая 2009

Мне нужно кодировать / декодировать байтовые массивы UTF-16 в java.lang.String. Массивы байтов передаются мне с меткой порядка байтов (BOM) , и мне нужно кодировать байтовые массивы с помощью спецификации.

Кроме того, поскольку я имею дело с клиент-сервером Microsoft, я бы хотел использовать кодирование с прямым порядком байтов (вместе с LE BOM), чтобы избежать недоразумений. Я понимаю, что с BOM она должна работать с прямым порядком байтов, но я не хочу плыть вверх по течению в мире Windows.

В качестве примера, вот метод, который кодирует java.lang.String как UTF-16 с прямым порядком байтов с спецификацией:

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? В идеале я бы хотел избежать копирования всего байтового массива в новый байтовый массив, в начале которого выделены два дополнительных байта.

То же самое касается декодирования такой строки, но это гораздо проще, если использовать java.lang.String конструктор :

public String(byte[] bytes,
              int offset,
              int length,
              String charsetName)

Ответы [ 5 ]

27 голосов
/ 19 мая 2009

Имя кодировки "UTF-16" всегда будет кодироваться с помощью спецификации и будет декодировать данные с использованием либо байтов с большим / меньшим порядковым номером, но "UnicodeBig" и "UnicodeLittle" полезны для кодирования в определенном порядке байтов. Используйте UTF-16LE или UTF-16BE без спецификации - см. Этот пост , чтобы узнать, как использовать \ uFEFF для обработки спецификаций вручную. См. здесь для канонического именования имен строк кодировки или (предпочтительно) класса Charset . Также обратите внимание, что только 10000 * ограниченное подмножество кодировок абсолютно необходимо для поддержки.

7 голосов
/ 19 мая 2009

Вот как вы это делаете в nio:

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

Конечно, это должно быть быстрее, но я не знаю, сколько массивов он делает под прикрытием, но я понимаю смысл API в том, что он должен минимизировать это.

6 голосов
/ 19 мая 2009

Во-первых, для декодирования вы можете использовать набор символов "UTF-16"; это автоматически обнаруживает начальную спецификацию. Для кодирования UTF-16BE вы также можете использовать набор символов «UTF-16» - он напишет правильную спецификацию, а затем выведет данные с прямым порядком байтов.

Для кодирования с прямым порядком байтов с помощью спецификации, я не думаю, что ваш текущий код слишком плох, даже с двойным распределением (если ваши строки не являются действительно чудовищными). То, что вы можете захотеть сделать, если они не связаны с байтовым массивом, а с ByteBuffer java.nio и используют класс java.nio.charset.CharsetEncoder (Что вы можете получить из Charset.forName («UTF-16LE»). NewEncoder ()).

2 голосов
/ 19 мая 2009
    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, чтобы изо всех сил знать, как вы это делаете.

0 голосов
/ 25 августа 2017

Это старый вопрос, но все же я не смог найти приемлемый ответ для моей ситуации. По сути, в Java нет встроенного кодировщика для 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();
}
...