Можно ли назначить 4 байта с порядком байтов с порядком байтов без знака в примитиве Java, используя только побитовые операторы?

Я хочу прочитать 4 байта, которые являются прямым порядком кодирования 32-разрядного целого числа без знака, и присвоить значение Java int

(Да, на самом деле я буду использовать "long", но в этом случае я "знаю", что значение без знака никогда не бывает таким большим, что оно будет переполнено знаком int в нотации дополнения до двух, и это подходит для моей иллюстрации использования int).

4 рассматриваемых байта кодируют значение "216" в стиле little-endian:

0xD8000000

И в основном мне просто нужно вставить следующий битовый шаблон в Java int:

0x000000D8

Следующий простой код должен сделать это... и для первых трех байтов "0x00" это успешно выполняется:

byte b1 = din.readByte();
byte b2 = din.readByte();
byte b3 = din.readByte();
byte b4 = din.readByte();
int s = 0;
s = s | b4;
s = (s << 8);
s = s | b3;
s = (s << 8);
s = s | b2;
s = (s << 8);
s = s | b1;
return s;

Тем не менее, он облажается:

s = s | b1;

... потому что биты b1 равны 1101 1000, что является отрицательным числом (-40) в двоичной записи дополнения, поскольку наиболее значимый бит равен 1. Когда Java расширяет b1 до целого числа перед вычислением побитового или оператора |, -40 кодируется как 0xFFFFFFD8, что порождает наше наивное предположение, что первые 3 байта расширенного целого числа будут 0.

Так что моя стратегия на мель. Но что мне делать вместо этого? Возможно ли даже решить эту проблему с помощью примитивных операторов (пожалуйста, дайте решение), или мы должны прибегнуть к библиотеке классов? (Я не слишком много играю с битами и байтами напрямую в моем обычном кодировании, поэтому мне не хватает идиомы для того, что должно быть "повседневным" кодом).

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

ByteBuffer b = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).put((byte) 0xD8).put((byte) 0x00).put((byte) 0x00).put((byte) 0x00);
b.flip();
int s = b.getInt();

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

Спасибо! Дэвид.

2 ответа

Решение

Просто включите & 0xff для каждого байта в продвижение int, чтобы убедиться, что старшие биты установлены в 0:

byte b1 = din.readByte();
byte b2 = din.readByte();
byte b3 = din.readByte();
byte b4 = din.readByte();
int s = 0;
s = s | (b4 & 0xff);
s = (s << 8);
s = s | (b3 & 0xff);
s = (s << 8);
s = s | (b2 & 0xff);
s = (s << 8);
s = s | (b1 & 0xff);
return s;

Или более компактно:

byte b1 = din.readByte();
byte b2 = din.readByte();
byte b3 = din.readByte();
byte b4 = din.readByte();
return ((b4 & 0xff) << 24)
     | ((b3 & 0xff) << 16)
     | ((b2 & 0xff) << 8)
     | ((b1 & 0xff) << 0);

(Очевидно, что "сдвиг влево 0" не нужен, но он сохраняет согласованность выше.)

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

      public long ToLittleEndian32Java(byte[] a) {
    int idx = 0;
    long ret = (a[idx++] & 0xFF);
    ret |= (long) (a[idx++] & 0xFF) << 8;
    ret |= (long) (a[idx++] & 0xFF) << 16;
    ret |= (long) (a[idx++] & 0xFF) << 24;
    return ret;
}
Другие вопросы по тегам