Статьи

Java Best Practices — Преобразование символов в байты и байты в символы

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

Все обсуждаемые темы основаны на сценариях использования, полученных в результате разработки критически важных, высокопроизводительных производственных систем для телекоммуникационной отрасли.

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

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

  • Система: openSUSE 11.1 (x86_64)
  • Процессор (ЦП): Процессор Intel® Core ™ 2 Duo T6670 с частотой 2,20 ГГц
  • Скорость процессора: 1200,00 МГц
  • Общий объем памяти (ОЗУ): 2,8 ГБ
  • Java: OpenJDK 1.6.0_0 64-битная

Применяется следующая тестовая конфигурация:

  • Одновременный рабочий Темы: 1
  • Тест повторений на одного работника Тема: 1000000
  • Всего тестовых прогонов: 100

Преобразование символов в байты и байты в символы
Преобразование между байтами и байтами в символы считается распространенной задачей среди разработчиков Java, которые программируют в сетевой среде, манипулируют потоками байтовых данных, сериализуют объекты String , реализуют протоколы связи и т. Д. По этой причине Java предоставляет несколько утилит, которые позволяют Разработчик для преобразования String (или символьного массива) в его байтовый массив, эквивалентный и наоборот.

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

Другой «классический» подход для преобразования символьного массива в эквивалент байтового массива — использование класса ByteBuffer пакета NIO. Пример фрагмента кода для конкретного подхода будет представлен позже.

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

Когда все символы для преобразования являются символами ASCII, предложенный метод преобразования показан ниже:

1
2
3
4
5
6
7
8
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;
}

Полученный байтовый массив создается путем приведения каждого символьного значения к его байтовому эквиваленту, поскольку мы знаем, что все символы находятся в диапазоне ASCII (0–127), поэтому могут занимать только один Байт в размере.

Используя полученный байтовый массив, мы можем преобразовать обратно в исходную строку , используя «классический» конструктор строки « новая строка (байт []) »

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

01
02
03
04
05
06
07
08
09
10
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 байта в размере. Для преобразования строки в ее эквивалент байтового массива мы конвертируем каждый символ строки в его 2-байтовое представление.

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

1
2
3
4
5
6
7
8
9
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 из его 2-байтового представления. Используя полученный массив символов, мы можем преобразовать обратно в исходную строку , используя «классический» конструктор строки « новая строка (char []) »

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

1
2
3
4
5
6
7
8
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;
}
1
2
3
4
public static String bytesToStringUTFNIO(byte[] bytes) {
 CharBuffer cBuffer = ByteBuffer.wrap(bytes).asCharBuffer();
 return cBuffer.toString();
}

В заключительной части этой статьи мы предоставляем графики сравнения производительности для вышеупомянутых подходов преобразования строки в байтовый массив и байтового массива в строку . Мы протестировали все методы, используя входную строку « тестовая строка ».

Сначала сравнительная таблица производительности преобразования массива строк в байты:

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

Наконец, байтовый массив с таблицей сравнения производительности String :

Горизонтальная ось представляет количество тестовых прогонов, а вертикальная ось — среднее количество транзакций в секунду (TPS) для каждого тестового прогона. Таким образом, чем выше значения, тем лучше. Как и ожидалось, подходы « new String (byte []) » и « bytesToStringUTFNIO (byte []) » работали плохо по сравнению с предложенным подходом « bytesToStringUTFCustom (byte []) ». Как вы можете видеть, предлагаемый нами метод достиг почти 15% увеличения TPS по сравнению с методом « new String (byte []) » и почти 30% увеличения TPS по сравнению с методом « bytesToStringUTFNIO (byte []) ».

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

Удачного кодирования

Джастин

PS

Приняв во внимание предложение нескольких наших читателей использовать операцию « String.charAt (int) » вместо « String.toCharArray () » для преобразования символов String в байты, я изменил предложенные нами методы и повторно выполнили тесты. Как и ожидалось, дальнейшее повышение производительности будет достигнуто. В частности, дополнительное увеличение среднего значения TPS на 13% было зарегистрировано для метода « stringToBytesASCII (String) », а среднее увеличение показателя TPS на 2% было зафиксировано для « stringToBytesUTFCustom (String) ». Таким образом, вы должны использовать измененные методы, поскольку они работают даже лучше, чем оригинальные. Обновленные методы показаны ниже:

1
2
3
4
5
6
7
public static byte[] stringToBytesASCII(String str) {
 byte[] b = new byte[str.length()];
 for (int i = 0; i < b.length; i++) {
  b[i] = (byte) str.charAt(i);
 }
 return b;
}
01
02
03
04
05
06
07
08
09
10
public static byte[] stringToBytesUTFCustom(String str) {
 byte[] b = new byte[str.length() << 1];
 for(int i = 0; i < str.length(); i++) {
  char strChar = str.charAt(i);
  int bpos = i << 1;
  b[bpos] = (byte) ((strChar&0xFF00)>>8);
  b[bpos + 1] = (byte) (strChar&0x00FF);
 }
 return b;
}
Статьи по Теме :