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: