Pull to refresh

[Перевод] Java Best Practices. Преобразование Char в Byte и обратно

Reading time4 min
Views36K
Сайт Java Code Geeks изредка публикует посты в серии Java Best Practices — проверенные на production решения. Получив разрешение от автора, перевёл один из постов. Дальше — больше.


Продолжая серию статей о некоторых аспектах программирования на Java, мы коснёмся сегодня производительности String, особенно момент преобразования character в байт-последовательность и обратно в том случае, когда используется кодировка по умолчанию. В заключение мы приложим сравнение производительности между неклассическими и классическими подходами для преобразования символов в байт-последовательность и обратно.

Все изыскания базируются на проблемах в разработке крайне эффективных систем для задач в области телекоммуникации (ultra high performance production systems for the telecommunication industry).

Перед каждой из частей статьи очень рекомендуем ознакомиться с Java API для дополнительной информации и примеров кода.

Эксперименты проводились на Sony Vaio со следующими характеристиками:
ОС: openSUSE 11.1 (x86_64)
Процессор (CPU): Intel® Core(TM)2 Duo CPU T6670 @ 2.20GHz
Частота: 1,200.00 MHz
ОЗУ (RAM): 2.8 GB
Java: OpenJDK 1.6.0_0 64-Bit

Со следующими параметрами:
Одновременно тредов: 1
Количество итераций эксперимента: 1000000
Всего тестов: 100


Преобразование Char в Byte и обратно:



Задача преобразования Char в Byte и обратно широко распространена в области коммуникаций, где программист обязан обрабатывать байтовые последовательности, сериализовать String-и, реализовывать протоколы и т.д.
Для этого в Java существует набор инструментов.

Метод «getBytes(charsetName)» класса String, наверное, один из популярнейших инструментов для преобразования String в его байтовый эквивалент. Параметр charsetName указывает на кодировку String, в случае отсутствия оного метод кодирует String в последовательность байт используя стоящую в ОС по умолчанию кодировку.

Ещё одним классическим подходом к преобразованию массива символов в его байтовый эквивалент является использование класса ByteBuffer из пакета NIO (New Input Output).

Оба подхода популярны и, безусловно, достаточно просты в использовании, однако испытывают серьёзные проблемы с производительностью по сравнению с более специфическими методами. Помните: мы не конвертируем из одной кодировки в другую, для этого вы должны придерживаться «классических» подходов с использованием либо «String.getBytes (charsetName)» либо возможностей пакета NIO.

В случае ASCII мы имеем следующий код:

public static byte[] stringToBytesASCII(String str) {

 char[] buffer = str.toCharArray();

 byte[] b = new byte[buffer.length];

 for (int i = 0; i < b.length; i++) {

  b[i] = (byte) buffer[i];

 }

 return b;

}


Массив b создаётся путём кастинга (casting) значения каждого символа в его байтовый эквивалент, при этом учитывая ASCII-диапазон (0-127) символов, каждый из которых занимает один байт.

Массив b можно преобразовать обратно в строку с помощью конструктора «new String(byte[])»:

System.out.println(new String(stringToBytesASCII("test")));


Для кодировки по умолчанию мы можем использовать следующий код:

public static byte[] stringToBytesUTFCustom(String str) {

 char[] buffer = str.toCharArray();

 byte[] b = new byte[buffer.length << 1];

 for(int i = 0; i < buffer.length; i++) {

  int bpos = i << 1;

  b[bpos] = (byte) ((buffer[i]&0xFF00)>>8);

  b[bpos + 1] = (byte) (buffer[i]&0x00FF);

 }

 return b;

}


Каждый символ в Java занимает 2 байта, для преобразования строки в байтовый эквивалент нужно перевести каждый символ строки в его двухбайтовый эквивалент.

И обратно в строку:

public static String bytesToStringUTFCustom(byte[] bytes) {

 char[] buffer = new char[bytes.length >> 1];

 for(int i = 0; i < buffer.length; i++) {

  int bpos = i << 1;

  char c = (char)(((bytes[bpos]&0x00FF)<<8) + (bytes[bpos+1]&0x00FF));

  buffer[i] = c;

 }

 return new String(buffer);

}



Мы восстанавливаем каждый символ строки из его двухбайтового эквивалента и затем, опять же с помощью конструктора String(char[]), создаём новый объект.

Примеры использования возможностей пакета NIO для наших задач:

public static byte[] stringToBytesUTFNIO(String str) {

 char[] buffer = str.toCharArray();

 byte[] b = new byte[buffer.length << 1];

 CharBuffer cBuffer = ByteBuffer.wrap(b).asCharBuffer();

 for(int i = 0; i < buffer.length; i++)

  cBuffer.put(buffer[i]);

 return b;

}

public static String bytesToStringUTFNIO(byte[] bytes) {

 CharBuffer cBuffer = ByteBuffer.wrap(bytes).asCharBuffer();

 return cBuffer.toString();

}


А теперь, как и обещали, графики.

String в byte array:



Ось абсцисс — количество тестов, ординат — количество операций в секунду для каждого теста. Что выше — то быстрее. Как и ожидалось, «String.getBytes()» и «stringToBytesUTFNIO(String)» отработали куда хуже «stringToBytesASCII(String)» и «stringToBytesUTFCustom(String)». Наши реализации, как можно увидеть, добились почти 30% увеличения количества операций в секунду.

Byte array в String:



Результаты опять же радуют. Наши собственные методы добились 15% увеличения количества операций в секунду по сравнению с «new String(byte[])» и 30% увеличения количества операций в секунду по сравнению с «bytesToStringUTFNIO(byte[])».

В качестве вывода: в том случае, если вам необходимо преобразовать байтовую последовательность в строку или обратно, при этом не требуется менять кодировки, вы можете получить замечательный выигрыш в производительности с помощью самописных методов. В итоге, наши методы добились в общем 45% ускорения по сравнению с классическими подходами.

Счастливого кодинга.

Tags:
Hubs:
+25
Comments35

Articles