Продолжая серию статей о предлагаемых практиках при работе с языком программирования 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 | publicstaticbyte[] stringToBytesASCII(String str) { char[] buffer = str.toCharArray(); byte[] b = newbyte[buffer.length]; for(inti = 0; i < b.length; i++) {  b[i] = (byte) buffer[i]; } returnb;} | 
Полученный байтовый массив создается путем приведения каждого символьного значения к его байтовому эквиваленту, поскольку мы знаем, что все символы находятся в диапазоне ASCII (0–127), поэтому могут занимать только один Байт в размере.
Используя полученный байтовый массив, мы можем преобразовать обратно в исходную строку , используя «классический» конструктор строки « новая строка (байт []) »
Для кодировки символов по умолчанию мы можем использовать методы, показанные ниже, чтобы преобразовать строку в байтовый массив и наоборот:
| 01 02 03 04 05 06 07 08 09 10 | publicstaticbyte[] stringToBytesUTFCustom(String str) { char[] buffer = str.toCharArray(); byte[] b = newbyte[buffer.length << 1]; for(inti = 0; i < buffer.length; i++) {  intbpos = i << 1;  b[bpos] = (byte) ((buffer[i]&0xFF00)>>8);  b[bpos + 1] = (byte) (buffer[i]&0x00FF); } returnb;} | 
Каждый тип символов в Java занимает 2 байта в размере. Для преобразования строки в ее эквивалент байтового массива мы конвертируем каждый символ строки в его 2-байтовое представление.
Используя полученный байтовый массив, мы можем преобразовать обратно в исходную строку , используя метод, представленный ниже:
| 1 2 3 4 5 6 7 8 9 | publicstaticString bytesToStringUTFCustom(byte[] bytes) { char[] buffer = newchar[bytes.length >> 1]; for(inti = 0; i < buffer.length; i++) {  intbpos = i << 1;  charc = (char)(((bytes[bpos]&0x00FF)<<8) + (bytes[bpos+1]&0x00FF));  buffer[i] = c; } returnnewString(buffer);} | 
Мы строим каждый символ String из его 2-байтового представления. Используя полученный массив символов, мы можем преобразовать обратно в исходную строку , используя «классический» конструктор строки « новая строка (char []) »
Наконец, что не менее важно, мы предоставляем два примера методов, использующих пакет NIO, чтобы преобразовать строку в ее эквивалент байтового массива и наоборот:
| 1 2 3 4 5 6 7 8 | publicstaticbyte[] stringToBytesUTFNIO(String str) { char[] buffer = str.toCharArray(); byte[] b = newbyte[buffer.length << 1]; CharBuffer cBuffer = ByteBuffer.wrap(b).asCharBuffer(); for(inti = 0; i < buffer.length; i++)  cBuffer.put(buffer[i]); returnb;} | 
| 1 2 3 4 | publicstaticString bytesToStringUTFNIO(byte[] bytes) { CharBuffer cBuffer = ByteBuffer.wrap(bytes).asCharBuffer(); returncBuffer.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 | publicstaticbyte[] stringToBytesASCII(String str) { byte[] b = newbyte[str.length()]; for(inti = 0; i < b.length; i++) {  b[i] = (byte) str.charAt(i); } returnb;} | 
| 01 02 03 04 05 06 07 08 09 10 | publicstaticbyte[] stringToBytesUTFCustom(String str) { byte[] b = newbyte[str.length() << 1]; for(inti = 0; i < str.length(); i++) {  charstrChar = str.charAt(i);  intbpos = i << 1;  b[bpos] = (byte) ((strChar&0xFF00)>>8);  b[bpos + 1] = (byte) (strChar&0x00FF);  } returnb;} | 
- Лучшие практики Java — DateFormat в многопоточной среде
- Java Best Practices — высокопроизводительная сериализация
- Лучшие практики Java — Вектор против ArrayList против HashSet
- Java Best Practices — строковая производительность и точное совпадение строк
- Лучшие практики Java — битва в очереди и связанный ConcurrentHashMap

