Java: преобразование строки в / из ByteBuffer и связанные с этим проблемы - PullRequest
77 голосов
/ 10 августа 2009

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

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's 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 каждый раз, когда выполняется преобразование. Нужно ли синхронизировать эти методы? Есть ли лучший способ конвертировать между Strings и ByteBuffers? Спасибо!

Ответы [ 3 ]

51 голосов
/ 10 августа 2009

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

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

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

27 голосов
/ 16 июня 2015

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

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

14 голосов
/ 26 сентября 2012

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

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

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

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

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

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

приписка

ссылки на документы Java:

  1. Метод кодирования (удобство): http://docs.oracle.com/javase/6/docs/api/java/nio/charset/CharsetEncoder.html#encode%28java.nio.CharBuffer%29
  2. Общий метод кодирования: http://docs.oracle.com/javase/6/docs/api/java/nio/charset/CharsetEncoder.html#encode%28java.nio.CharBuffer,%20java.nio.ByteBuffer,%20boolean%29
...