Ищу объяснение Long -> Byte Array -> String -> Byte Array -> Long - PullRequest
1 голос
/ 03 августа 2020

Я ищу объяснение какой-то странности, которую я видел в чьем-то коде elses, они извлекали значение "int64" из сторонней библиотеки, читая из атрибута LDAP, эта библиотека вернула массив байтов. Чтобы получить значение, они пробовали что-то вроде

String s = new String(bytesFrom3rdParty);
BigInteger i = new BigInteger(s.getBytes());
System.out.println(i.toString());

С некоторыми длинными значениями это давало неверный результат, которого не ожидалось. Для меня выделялись две вещи:

  1. Почему go из байтового массива -> String -> Bytes -> BigInteger
  2. Зачем использовать BigInteger для 64-битных чисел c значение.

В любом случае я провел небольшой эксперимент

private static byte[] longToByteArray(Long l) {
    return ByteBuffer.allocate(Long.SIZE / Byte.SIZE).putLong(l).array();
}

private static Long byteArrayToLong(byte[] bytes) {
    return ByteBuffer.wrap(bytes).getLong();
}

public static void main(String[] args) {
    
    for (long l = 0L; l < 1000; l++) {
        byte[] origBytes = longToByteArray(l);
        String s = new String(origBytes);
        byte[] stringBytes = s.getBytes();
        Long origL = byteArrayToLong(origBytes);
        Long stringL = byteArrayToLong(stringBytes);
        System.out.println(origL.toString() + " " + stringL.toString());
    }
    
}

Как я и подозревал, пропуск преобразования в строку, а затем обратно в массив байтов, устранил проблему, вывод из выше это что-то вроде

124 124
125 125
126 126
127 127
128 239
129 239
130 239
131 239
132 239

И затем значение правой руки снова исправляется, когда оно достигает 256

254 239
255 239
256 256
257 257
258 258
259 259
260 260
261 261
262 262
263 263
264 264

Итак, пара вопросов от меня

  1. Почему значение правой руки неверно? Я предполагаю, что это как-то связано с преобразованием между 64-битным длинным значением в 32-битное строковое значение?
  2. Почему неправильное значение не изменяется, пока значение l не достигнет 256?

Ответы [ 2 ]

2 голосов
/ 03 августа 2020

Давайте немного упростим: byte [] -> String -> byte [] выполняет кодирование и декодирование. Когда вы используете новую строку (byte [] b) , она будет:

Создает новую строку путем декодирования указанного массива байтов с использованием кодировки платформы по умолчанию.

Что произойдет, если символ отсутствует в наборе символов по умолчанию вашей платформы?

Поведение этого конструктора, когда данные байты недопустимы в кодировке по умолчанию, не определено.

Итак, в вашей ситуации, когда передается недопустимый байт, он преобразует символ в 65533 символ замены java.

byte[] b = {-1};
System.out.println( Arrays.toString( new String(b).getBytes() ) );

[- 17, -65, -67]

Вот почему значение не меняется, все они отображаются на заменяющий символ.

Вы можете использовать BigInteger простой доступ к конструктору, который принимает byte[] для создания лонг.

2 голосов
/ 03 августа 2020

byte[] могут быть разными, например:

  • сериализованное значение String (например, кодировка UTF-8) «123» -> байты, представляющие строку, которая фактически кодирует каждый символ 2 байтами
  • сериализованное значение Long в двоичном формате 123 -> 8 байтов, представляющих одно число

Итак, когда имеет смысл преобразовать байт [ ] в String - это когда вы фактически получаете String в byte [], а после этого вы анализируете String на число (в вашем случае BigInteger). Возвращение к байтам не имеет для меня особого смысла.

String s = new String(bytesFrom3rdParty); // binary from UTF-8 string
BigInteger i = new BigInteger(s); // parse String "123" to BigInteger
System.out.println(i.toString()); // now i will be 123 in BigInteger

Это тоже сработает:

String s = new String(bytesFrom3rdParty); // binary from UTF-8 string
Long i = Long.parseLong(s); // parse String "123" to Long
System.out.println(i.toString()); // now i will be 123 in Long

То, что вы делаете в своем примере, - это второй случай, вы сериализуете Длинное двоичное значение до byte[] (не строка UTF-8). Затем вы создаете строку из этих двоичных данных и получаете байты. То, что происходит, происходит из-за преобразования в поддерживающую реализацию Charset Java - он ожидает, что это будет действительная кодировка Charset, она изменяет ваше двоичное представление на то, что соответствует кодировке Charset.

Когда вы пытаетесь получить его обратно и построить Long из него ломается, почему 128. Вероятно, до 127 (старый стандарт ASCII имел такое количество символов) ваше двоичное представление каким-то образом соответствует кодировке Java, но после того, как оно ломается.

  • сериализованное строковое значение должно быть проанализировано Long.parseFrom(String) или new BigInteger(String)
  • двоичный Сериализованный номер должен быть прочитан двоичным ByteBuffer.getLong()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...