Java - Язык:Преобразование строки в ByteBuffer и из него и связанные с этим проблемы
-
12-09-2019 - |
Вопрос
Я использую Java NIO для своих подключений к сокетам, и мой протокол основан на тексте, поэтому мне нужно иметь возможность преобразовывать строки в байт-буферы перед их записью в 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'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?Спасибо!
Решение
Ознакомьтесь с CharsetEncoder
и CharsetDecoder
Описания API - Вы должны следовать определенная последовательность вызовов метода чтобы избежать этой проблемы.Например, для CharsetEncoder
:
- Сбросьте кодировщик с помощью
reset
метод, если только он не использовался ранее; - Вызвать
encode
метод ноль или более раз, при условии, что могут быть доступны дополнительные входные данные, передаваяfalse
для аргумента endOfInput и заполнения входного буфера и очистки выходного буфера между вызовами; - Вызвать
encode
метод в последний раз, проходяtrue
для аргумента endOfInput;и тогда - Вызвать
flush
метод, позволяющий кодеру сбрасывать любое внутреннее состояние в выходной буфер.
Кстати, это тот же подход, который я использую для NIO, хотя некоторые из моих коллег преобразуют каждый символ непосредственно в байт, зная, что они используют только ASCII, что, как я могу себе представить, вероятно, быстрее.
Другие советы
Если что-то не изменилось, вам лучше с
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 в зависимости от вашего варианта использования.На практике, если вы действительно не хотите, чтобы это работало ни при каких обстоятельствах, безопасно оптимизировать ненужную вам ветку.
Ответ Адамски является хорошим и описывает шаги в операции кодирования при использовании общего метода encode (который принимает байтовый буфер в качестве одного из входных данных)
Однако рассматриваемый метод (в этом обсуждении) является вариантом encode - кодирование (ввод буфера символов).Это такой удобный метод, который реализует всю операцию кодирования.(Пожалуйста, смотрите ссылку на java docs в P.S.)
Согласно документам, Поэтому этот метод не следует вызывать, если операция кодирования уже выполняется (именно это и происходит в коде ZenBlender - использование статического кодировщика / декодера в многопоточной среде).
Лично мне нравится использовать удобство методы (по сравнению с более общими методами кодирования / декодирования), поскольку они снимают с себя нагрузку, выполняя все шаги под прикрытием.
ЗенБлендер и Адамски уже предложили несколько способов безопасно сделать это в своих комментариях.Перечисляя их все здесь:
- Создавайте новый объект кодера / декодера, когда это необходимо для каждой операции (неэффективно, поскольку это может привести к большому количеству объектов).или,
- Используйте ThreadLocal, чтобы избежать создания нового кодера / декодера для каждой операции.или,
- Синхронизируйте всю операцию кодирования / декодирования (это может быть нежелательно, если только ваша программа не согласится пожертвовать некоторым параллелизмом).
P.S.
ссылки на документы java docs:
- Метод кодирования (удобства): http://docs.oracle.com/javase/6/docs/api/java/nio/charset/CharsetEncoder.html#encode%28java.nio.CharBuffer%29
- Общий метод кодирования: http://docs.oracle.com/javase/6/docs/api/java/nio/charset/CharsetEncoder.html#encode%28java.nio.CharBuffer,%20java.nio.ByteBuffer,%20boolean%29