Понимание Java-байтов - PullRequest
       17

Понимание Java-байтов

8 голосов
/ 02 октября 2010

Итак, вчера на работе мне пришлось написать приложение для подсчета страниц в файле AFP. Таким образом, я стёр со своего PDF-файла спецификации MO: DCA и нашел структурированное поле BPG (Begin Page) и его 3-байтовый идентификатор. Приложение должно работать на AIX, поэтому я решил написать его на Java.

Для максимальной эффективности я решил прочитать первые 6 байтов каждого структурированного поля, а затем пропустить оставшиеся байты в поле. Это дало бы мне:

0: Start of field byte
1-2: 2-byte length of field
3-5: 3-byte sequence identifying the type of field

Итак, я проверяю тип поля и увеличиваю счетчик страниц, если он равен BPG, а я нет, если это не так. Затем я пропускаю оставшиеся байты в поле, а не читаю их. И здесь, в пропуске (и действительно в длине поля), я обнаружил, что Java использует подписанные байты.

Я немного погуглил и нашел довольно много полезной информации. Самой полезной, конечно же, была инструкция сделать побитовые & до 0xff, чтобы получить значение типа unsigned int. Это было необходимо для получения длины, которую можно использовать в расчете для количества пропускаемых байтов.

Теперь я знаю, что в 128 мы начинаем считать в обратном направлении от -128. Я хочу знать, как здесь работает побитовая операция, точнее, как я получаю двоичное представление для отрицательного числа.

Если я правильно понимаю побитовый &, ваш результат будет равен числу, в котором установлены только общие биты ваших двух чисел. Итак, предполагая byte b = -128, мы бы получили:

b & 0xff // 128

1000 0000-128
1111 1111 255
---------
1000 0000 128

Так как бы я прибыл в 1000 0000 за -128? Как бы я получил двоичное представление чего-то менее очевидного, например, -72 или -64?

Ответы [ 6 ]

18 голосов
/ 02 октября 2010

Чтобы получить двоичное представление отрицательного числа, вы вычисляете дополнение к двум:

  • Получите двоичное представление положительного числа
  • Инвертируйте все биты
  • Добавить один

Давайте сделаем -72 в качестве примера:

0100 1000    72
1011 0111    All bits inverted
1011 1000    Add one

Таким образом, двоичное (8-битное) представление -72 равно 10111000.

На самом деле с вами происходит следующее: в вашем файле есть байт со значением 10111000.Когда интерпретируется как неподписанный байт (что, вероятно, то, что вы хотите), это 88.

В Java, когда этот байт используется как int (например, потому что read() возвращает int или из-занеявное продвижение), оно будет интерпретироваться как подписанный байт и расширено до 11111111 11111111 11111111 10111000.Это целое число со значением -72.

Путем AND с 0xff вы сохраняете только самые младшие 8 битов, поэтому ваше целое число теперь равно 00000000 00000000 00000000 10111000, что составляет 88.

2 голосов
/ 02 октября 2010

Что я хочу знать, так это то, как здесь работает побитовая операция, точнее, как я получаю двоичное представление для отрицательного числа.

Двоичное представление отрицательного числа - это представление соответствующего положительного числа, перевернутого битами с добавленным к нему 1. Это представление называется дополнением до двух .

1 голос
/ 02 октября 2010

Не уверен, что вы действительно хотите :) Я предполагаю, что вы спрашиваете, как извлечь многобайтовое значение со знаком? Сначала посмотрите, что происходит, когда вы подписываете расширение одного байта:

byte[] b = new byte[] { -128 };
int i = b[0];
System.out.println(i); // prints -128!

Итак, знак корректно расширяется до 32 бит, не делая ничего особенного. Байт 1000 0000 корректно расширяется до 1111 1111 1111 1111 1111 1111 1000 0000. Вы уже знаете, как подавить расширение знака с помощью AND с 0xFF - для многобайтовых значений вы хотите, чтобы только знак старшего значащего байта был расширением, и менее значимые байты, которые вы хотите считать беззнаковыми (пример предполагает сетевой байт порядок, 16-битное значение типа int):

byte[] b = new byte[] { -128, 1 }; // 0x80, 0x01
int i = (b[0] << 8) | (b[1] & 0xFF);
System.out.println(i); // prints -32767!
System.out.println(Integer.toHexString(i)); // prints ffff8001

Вам нужно подавить расширение знака для каждого байта, кроме самого старшего, чтобы извлечь 32-битное целое число со знаком в 64-битную длину:

byte[] b = new byte[] { -54, -2, -70, -66 }; // 0xca, 0xfe, 0xba, 0xbe
long l = ( b[0]         << 24) |
         ((b[1] & 0xFF) << 16) |
         ((b[2] & 0xFF) <<  8) |
         ((b[3] & 0xFF)      );
System.out.println(l); // prints -889275714
System.out.println(Long.toHexString(l)); // prints ffffffffcafebabe

Примечание: в системах на базе Intel байты часто хранятся в обратном порядке (сначала младший байт), поскольку архитектура x86 хранит большие объекты в этом порядке в памяти. Многие программы x86 тоже используют его в форматах файлов.

1 голос
/ 02 октября 2010

Полагаю, магия здесь в том, что байт хранится в большем контейнере, вероятно, 32-битном int.И если байт был интерпретирован как байт со знаком, он расширяется для представления того же числа в 32-битном int, то есть если старший значащий бит (первый) байта равен 1, то в 32-битном int всебиты слева от этой 1 также обращаются в 1 (это связано с тем, как представлены отрицательные числа, дополнение к двум).

Теперь, если вы & 0xFF, что int, вы отрежете эти 1 и в итоге«положительное» значение int, представляющее прочитанное вами значение байта.

0 голосов
/ 02 октября 2010

Для байтов с установленным битом 7:

unsigned_value = signed_value + 256

Математически, когда вы вычисляете с байтами, вы вычисляете по модулю 256. Разница между знаковым и беззнаковым состоит в том, что вы выбираете разных представителей для классов эквивалентности, в то время как базовое представление в виде битового шаблона остается одинаковым для каждого класса эквивалентности. Это также объясняет, почему сложение, вычитание и умножение имеют тот же результат, что и битовая комбинация, независимо от того, выполняете ли вы вычисления со знаком или без знака.

0 голосов
/ 02 октября 2010

Для получения значения байта без знака вы можете либо:

int u = b & 0xFF;

, либо

int u = b < 0 ? b + 256 : b;
...