Перетворити рядок на байтовий масив і змінити його в Java

1. Вступ

Нам часто потрібно перетворювати між рядком та байтовим масивом у Java. У цьому посібнику ми детально розглянемо ці операції.

Спочатку ми розглянемо різні способи перетворення рядка в байтовий масив. Потім ми розглянемо подібні операції в зворотному порядку.

2. Перетворення рядка в байтовий масив

Рядок зберігається в вигляді масиву символів Unicode в Java. Щоб перетворити його в байтовий масив, ми переводимо послідовність символів у послідовність байтів. Для цього перекладу ми використовуємо екземпляр Charset . Цей клас задає відображення між послідовністю символів char і послідовністю байтів s .

Ми називаємо вищезазначений процес кодуванням .

Ми можемо кодувати рядок у байтовий масив на Java різними способами. Давайте розглянемо кожен із них детально на прикладах.

2.1. Використання String.getBytes ()

Клас String забезпечує три перевантажені методи getBytes для кодування рядка в байтовий масив :

  • getBytes () - кодує, використовуючи стандартну кодировку платформи
  • getBytes (String charsetName) - кодує, використовуючи названий набір символів
  • getBytes (Charset charset) - кодує, використовуючи надану кодировку

По-перше, давайте кодувати рядок, використовуючи набір символів платформи за замовчуванням:

String inputString = "Hello World!"; byte[] byteArrray = inputString.getBytes();

Вищевказаний метод залежить від платформи, оскільки використовує стандартний набір коду платформи. Ми можемо отримати цю кодировку, зателефонувавши Charset.defaultCharset () .

По-друге, давайте кодувати рядок, використовуючи іменовану кодировку:

@Test public void whenGetBytesWithNamedCharset_thenOK() throws UnsupportedEncodingException { String inputString = "Hello World!"; String charsetName = "IBM01140"; byte[] byteArrray = inputString.getBytes("IBM01140"); assertArrayEquals( new byte[] { -56, -123, -109, -109, -106, 64, -26, -106, -103, -109, -124, 90 }, byteArrray); }

Цей метод видає UnsupportedEncodingException, якщо названий набір символів не підтримується.

Поведінка вищезазначених двох версій невизначена, якщо вхідні дані містять символи, які не підтримуються набором символів. На відміну від цього, у третій версії для кодування непідтримуваного вводу використовується масив байтів заміни символів, встановлений за замовчуванням.

Далі викличемо третю версію методу getBytes () і передамо екземпляр Charset:

@Test public void whenGetBytesWithCharset_thenOK() { String inputString = "Hello ਸੰਸਾਰ!"; Charset charset = Charset.forName("ASCII"); byte[] byteArrray = inputString.getBytes(charset); assertArrayEquals( new byte[] { 72, 101, 108, 108, 111, 32, 63, 63, 63, 63, 63, 33 }, byteArrray); }

Тут ми використовуємо заводський метод Charset.forName, щоб отримати екземпляр Charset . Цей метод видає виняток виконання, якщо ім'я запитуваного коду є недійсним. Він також видає виняток виконання, якщо набір символів підтримується в поточній JVM.

Однак деякі набори символів гарантовано будуть доступні на кожній платформі Java. Клас StandardCharsets визначає константи для цих наборів символів.

Нарешті, давайте кодувати, використовуючи одну зі стандартних наборів символів:

@Test public void whenGetBytesWithStandardCharset_thenOK() { String inputString = "Hello World!"; Charset charset = StandardCharsets.UTF_16; byte[] byteArrray = inputString.getBytes(charset); assertArrayEquals( new byte[] { -2, -1, 0, 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 100, 0, 33 }, byteArrray); }

Таким чином, ми завершуємо огляд різних версій getBytes . Далі розглянемо метод, запропонований самим Шарсетом .

2.2. Використання Charset.encode ()

Клас Charset забезпечує encode () , зручний метод, який кодує символи Unicode у байти. Цей метод завжди замінює недійсні введені символи та символи, які неможливо відобразити, використовуючи масив байтів заміни символів, встановлений за замовчуванням.

Давайте використаємо метод кодування для перетворення рядка в байтовий масив:

@Test public void whenEncodeWithCharset_thenOK() { String inputString = "Hello ਸੰਸਾਰ!"; Charset charset = StandardCharsets.US_ASCII; byte[] byteArrray = charset.encode(inputString).array(); assertArrayEquals( new byte[] { 72, 101, 108, 108, 111, 32, 63, 63, 63, 63, 63, 33 }, byteArrray); }

Як ми бачимо вище, непідтримувані символи були замінені байтом заміни символів 63 за замовчуванням .

Дотепер використовувані підходи використовують клас CharsetEncoder внутрішньо для виконання кодування. Давайте розглянемо цей клас у наступному розділі.

2.3. CharsetEncoder

CharsetEncoder перетворює символи Unicode у послідовність байтів для даного коду . Більше того, це забезпечує чіткий контроль над процесом кодування .

Давайте використаємо цей клас для перетворення рядка в байтовий масив:

@Test public void whenUsingCharsetEncoder_thenOK() throws CharacterCodingException { String inputString = "Hello ਸੰਸਾਰ!"; CharsetEncoder encoder = StandardCharsets.US_ASCII.newEncoder(); encoder.onMalformedInput(CodingErrorAction.IGNORE) .onUnmappableCharacter(CodingErrorAction.REPLACE) .replaceWith(new byte[] { 0 }); byte[] byteArrray = encoder.encode(CharBuffer.wrap(inputString)) .array(); assertArrayEquals( new byte[] { 72, 101, 108, 108, 111, 32, 0, 0, 0, 0, 0, 33 }, byteArrray); }

Тут ми створюємо екземпляр CharsetEncoder , викликаючи метод newEncoder для об’єкта Charset .

Потім ми з зазначенням дії для умов помилки шляхом виклику onMalformedInput () і onUnmappableCharacter () методи . Ми можемо вказати такі дії:

  • IGNORE - скиньте помилкове введення
  • ЗАМЕНИТИ - замінити помилкове введення
  • ЗВІТ - повідомити про помилку, повернувши об'єкт CoderResult або викинувши CharacterCodingException

Крім того, ми використовуємо метод replaceWith (), щоб вказати масив байтів заміни .

Таким чином, ми завершуємо огляд різних підходів до перетворення рядка в байтовий масив. Давайте далі розглянемо зворотну операцію.

3. Перетворення байтового масиву в рядок

Йдеться про процес перетворення байтів масиву в рядки , як декодування . Подібно до кодування, для цього процесу потрібна Charset .

Однак ми не можемо просто використовувати будь-які набори символів для декодування байтового масиву. Ми повинні використовувати набір символів, який був використаний для кодування рядка в байтовий масив .

Ми можемо перетворити байтовий масив у рядок різними способами. Давайте розглянемо кожен із них детально.

3.1. Використання конструктора рядків

The String class has few constructors which take a byte array as input. They are all similar to the getBytes method but work in reverse.

First, let's convert a byte array to String using the platform's default charset:

@Test public void whenStringConstructorWithDefaultCharset_thenOK() { byte[] byteArrray = { 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 }; String string = new String(byteArrray); assertNotNull(string); }

Note that we don't assert anything here about the contents of the decoded string. This is because it may decode to something different, depending on the platform's default charset.

For this reason, we should generally avoid this method.

Secondly, let's use a named charset for decoding:

@Test public void whenStringConstructorWithNamedCharset_thenOK() throws UnsupportedEncodingException { String charsetName = "IBM01140"; byte[] byteArrray = { -56, -123, -109, -109, -106, 64, -26, -106, -103, -109, -124, 90 }; String string = new String(byteArrray, charsetName); assertEquals("Hello World!", string); }

This method throws an exception if the named charset is not available on the JVM.

Thirdly, let's use a Charset object to do decoding:

@Test public void whenStringConstructorWithCharSet_thenOK() { Charset charset = Charset.forName("UTF-8"); byte[] byteArrray = { 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 }; String string = new String(byteArrray, charset); assertEquals("Hello World!", string); }

Finally, let's use a standard Charset for the same:

@Test public void whenStringConstructorWithStandardCharSet_thenOK() { Charset charset = StandardCharsets.UTF_16; byte[] byteArrray = { -2, -1, 0, 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 100, 0, 33 }; String string = new String(byteArrray, charset); assertEquals("Hello World!", string); }

So far, we have converted a byte array into a String using the constructor. Let's now look into the other approaches.

3.2. Using Charset.decode()

The Charset class provides the decode() method that converts a ByteBuffer to String:

@Test public void whenDecodeWithCharset_thenOK() { byte[] byteArrray = { 72, 101, 108, 108, 111, 32, -10, 111, 114, 108, -63, 33 }; Charset charset = StandardCharsets.US_ASCII; String string = charset.decode(ByteBuffer.wrap(byteArrray)) .toString(); assertEquals("Hello �orl�!", string); }

Here, the invalid input is replaced with the default replacement character for the charset.

3.3. CharsetDecoder

Усі попередні підходи для внутрішнього декодування використовують клас CharsetDecoder . Ми можемо використовувати цей клас безпосередньо для дрібного контролю процесу декодування :

@Test public void whenUsingCharsetDecoder_thenOK() throws CharacterCodingException { byte[] byteArrray = { 72, 101, 108, 108, 111, 32, -10, 111, 114, 108, -63, 33 }; CharsetDecoder decoder = StandardCharsets.US_ASCII.newDecoder(); decoder.onMalformedInput(CodingErrorAction.REPLACE) .onUnmappableCharacter(CodingErrorAction.REPLACE) .replaceWith("?"); String string = decoder.decode(ByteBuffer.wrap(byteArrray)) .toString(); assertEquals("Hello ?orl?!", string); }

Тут ми замінюємо невірні вводи та непідтримувані символи на “?”.

Якщо ми хочемо бути проінформованими у випадку недійсних входів, ми можемо змінити декодер як:

decoder.onMalformedInput(CodingErrorAction.REPORT) .onUnmappableCharacter(CodingErrorAction.REPORT)

4. Висновок

У цій статті ми досліджували різні способи перетворення рядка в байтовий масив і зворотного. Ми повинні вибрати відповідний метод на основі вхідних даних, а також рівня контролю, необхідного для недійсних входів.

Як завжди, повний вихідний код можна знайти на GitHub.