Как кодировать/декодировать массивы байтов UTF-16LE с помощью спецификации?

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

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

В качестве примера, здесь приведен метод, который кодирует a 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)

Ответ 1

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

Ответ 2

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

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

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

Ответ 3

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

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

Ответ 4

    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, чтобы узнать, как это сделать.

Ответ 5

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