Можно ли назначить 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;
}