Java: преобразование строки в и из ByteBuffer и связанных с ней проблем

Я использую Java NIO для своих сокетов, а мой протокол основан на тексте, поэтому мне нужно иметь возможность преобразовывать строки в ByteBuffers, прежде чем записывать их в SocketChannel, и конвертировать входящие байтовые байты обратно в строки. В настоящее время я использую этот код:

public static Charset charset = Charset.forName("UTF-8");
public static CharsetEncoder encoder = charset.newEncoder();
public static CharsetDecoder decoder = charset.newDecoder();

public static ByteBuffer str_to_bb(String msg){
  try{
    return encoder.encode(CharBuffer.wrap(msg));
  }catch(Exception e){e.printStackTrace();}
  return null;
}

public static String bb_to_str(ByteBuffer buffer){
  String data = "";
  try{
    int old_position = buffer.position();
    data = decoder.decode(buffer).toString();
    // reset buffer position to its original so it is not altered:
    buffer.position(old_position);  
  }catch (Exception e){
    e.printStackTrace();
    return "";
  }
  return data;
}

Это работает большую часть времени, но я задаю вопрос, является ли это предпочтительным (или самым простым) способом выполнения каждого направления этого преобразования или если есть другой способ попробовать. Иногда и, казалось бы, случайным образом, вызовы encode() и decode() будут java.lang.IllegalStateException: Current state = FLUSHED, new state = CODING_END исключение или подобное, даже если я использую новый объект ByteBuffer каждый раз, когда выполняется преобразование. Нужно ли мне синхронизировать эти методы? Любой лучший способ конвертировать между строками и ByteBuffers? Спасибо!

Ответ 1

Посмотрите CharsetEncoder и CharsetDecoder Описание API. Чтобы избежать этой проблемы, вы должны следовать определенной последовательности вызовов методов. Например, для CharsetEncoder:

  • Reset кодировщик с помощью метода reset, если только он не использовался ранее;
  • Вызов метода encode ноль или более раз, если доступен дополнительный вход, передавая false для аргумента endOfInput и заполняя входной буфер и очищая выходной буфер между вызовами;
  • Вызов метода encode один последний раз, передав true для аргумента endOfInput; и затем
  • Вызвать метод flush, чтобы кодер мог сбросить любое внутреннее состояние в выходной буфер.

Кстати, это тот же подход, который я использую для NIO, хотя некоторые из моих коллег конвертируют каждый char непосредственно в байт в знании, что они используют только ASCII, что я могу себе представить, вероятно, быстрее.

Ответ 2

Если ничего не изменилось, вам лучше с

public static ByteBuffer str_to_bb(String msg, Charset charset){
    return ByteBuffer.wrap(msg.getBytes(charset));
}

public static String bb_to_str(ByteBuffer buffer, Charset charset){
    byte[] bytes;
    if(buffer.hasArray()) {
        bytes = buffer.array();
    } else {
        bytes = new byte[buffer.remaining()];
        buffer.get(bytes);
    }
    return new String(bytes, charset);
}

Обычно buffer.hasArray() будет либо всегда true, либо всегда false в зависимости от вашего варианта использования. На практике, если вы действительно не хотите, чтобы он работал ни при каких обстоятельствах, он безопасен для оптимизации отрасли, в которой вы не нуждаетесь.

Ответ 3

Answer by Adamski является хорошим и описывает шаги в операции кодирования при использовании общего метода кодирования (который принимает байтовый буфер как один из входов)

Однако, рассматриваемый метод (в этом обсуждении) представляет собой вариант кодирования - encode (CharBuffer in). Это метод , который реализует всю операцию кодирования. (См. Ссылку на java docs в P.S.)

В соответствии с документами Этот метод не должен вызываться, если операция кодирования уже выполняется (что происходит в коде ZenBlender - с использованием статического энкодера/декодера в многопоточном среда).

Лично мне нравится использовать удобные методы (более общие методы кодирования/декодирования), поскольку они убирают бремя, выполняя все шаги под обложками.

ZenBlender и Adamski уже предложили несколько способов, чтобы безопасно сделать это в своих комментариях. Список их здесь:

  • Создайте новый объект кодера/декодера, когда это необходимо для каждой операции (неэффективно, поскольку это может привести к большому количеству объектов). ИЛИ,
  • Используйте ThreadLocal, чтобы избежать создания нового кодера/декодера для каждой операции. ИЛИ,
  • Синхронизировать всю операцию кодирования/декодирования (это может быть нецелесообразно, если не принести в жертву некоторую concurrency в порядке для вашей программы)

P.S.

Ссылки на java docs: