Skip to content
August 27, 2011 / ahriman hpc mode

Java Best Practices. Byte Char

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

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

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

Эксперименты проводились на Sony Vaio со следующими характеристиками:

ОС : openSUSE 11.1 (x86_64)
Процессор (CPU) : Intel(R) 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 <>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% ускорения по сравнению с классическими подходами.

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

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: