我正在尝试用Java读取二进制文件。我需要读取无符号8位值,无符号16位值和无符号32位值的方法。这样做的最佳(最快,最好看的代码)是什么?我在c ++中完成了这个并做了类似的事情:
uint8_t *buffer;
uint32_t value = buffer[0] | buffer[1] << 8 | buffer[2] << 16 | buffer[3] << 24;
但是在Java中,如果例如buffer [1]包含一个设置了符号位的值,则会导致问题,因为左移的结果是int(?)。而不是OR:仅在特定位置使用0xA5,或者:在0xFFFFA500或类似的位置,这会“损坏”两个顶部字节。
我现在有一个代码,如下所示:
public long getUInt32() throws EOFException, IOException {
byte[] bytes = getBytes(4);
long value = bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24);
return value & 0x00000000FFFFFFFFL;
}
如果我想转换四个字节0x67 0xA5 0x72 0x50,结果是0xFFFFA567而不是0x5072A567。
编辑:这很有效:
public long getUInt32() throws EOFException, IOException {
byte[] bytes = getBytes(4);
long value = bytes[0] & 0xFF;
value |= (bytes[1] << 8) & 0xFFFF;
value |= (bytes[2] << 16) & 0xFFFFFF;
value |= (bytes[3] << 24) & 0xFFFFFFFF;
return value;
}
但是,有没有更好的方法呢?对于像这样的简单事情,10位操作看起来很“有点”..(看看我在那里做了什么?)=)
答案 0 :(得分:3)
示例代码的问题在于,当您从字节隐式转换为long时,使用符号扩展进行转换,这意味着如果字节的第一位为1,则使用1而不是零填充long。通过使用转换为long来阻止符号扩展,您的代码可以完美地运行。
public static long byteAsULong(byte b) {
return ((long)b) & 0x00000000000000FFL;
}
public static long getUInt32(byte[] bytes) {
long value = byteAsULong(bytes[0]) | (byteAsULong(bytes[1]) << 8) | (byteAsULong(bytes[2]) << 16) | (byteAsULong(bytes[3]) << 24);
return value;
}
如果您小心,可以使用带符号的值来包含位。您需要避免的是任何形式或签名操作,例如算术和有符号位移。如果您需要将值打印为数字,请意识到所有内置的java方法都会导致大的无符号数字显示为负数。
最重要的是要了解所有这些,关于位移。向右移动时,>>
运算符将保持数字符号的两个赞美。这意味着如果最左边的位是1,则移入的位将是1而不是0。好消息是Java至少有一个无符号位移位运算符,它总是以零为单位移位,它是>>>
。例如:
int bits;
bits >>> 4;
永远记住一堆比特表达的数据是任意的。尽管Java的内部方法都将这些位视为两个恭维,但如果不使用它们中的任何一个,则有符号字节包含与放入它们完全相同的位。
答案 1 :(得分:1)
你有正确的想法,我认为没有任何明显的改善。如果你看一下java.io.DataInput.readInt
spec,他们就会有同样的代码。它们会切换<<
和&
的顺序,但标准。
除非使用内存映射区域,否则无法一次性从int
数组中读取byte
,这对于方式来说太过分了。
当然,您可以直接使用DataInputStream
而不是首先阅读byte[]
:
DataInputStream d = new DataInputStream(new FileInputStream("myfile"));
d.readInt();
DataInputStream
使用的是相反的字节序,因此您还需要进行一些Integer.reverseBytes
次调用。它不会更快,但它更清洁。
答案 2 :(得分:1)
更常规的版本首先将字节转换为无符号值作为整数:
public long getUInt32() throws EOFException, IOException {
byte[] bytes = getBytes(4);
long value =
((bytes[0] & 0xFF) << 0) |
((bytes[1] & 0xFF) << 8) |
((bytes[2] & 0xFF) << 16) |
((bytes[3] & 0xFF) << 24);
return value;
}
不要挂断位操作的数量,很可能编译器会优化那些字节操作。
此外,您不应仅使用long
来获取32位值以避免使用该符号,您可以使用int
并忽略它在大多数情况下都已签名的事实。请参阅this answer。