从byte数组提取指定位范围比特时结果不符的问题排查
我有一个16字节的byte数组,里面填充了各类跨字节边界的值,数据的二进制表示如下:
0011 | 0011 | 1110 | 1000 | 1100 | 1000 | 1100 | 1000 |
0100 | 1101 | 0010 | 1111 | 1010 | 0001 | 1111 | 1111 [ 0 - 63]0000 | 0101 | 0000 | 0011 | 0000 | 0011 | 1110 | 1000 | 1100 | 1000 |
1100 | 1000 | 0000 | 0001 | 0010 | 1100 [64 -127]
需要提取的值对应的比特位置范围:
0-3 | 4-5 | 6-15 | 16-23 | 24-31 | 32 - 63 | 64-71 | 72-79 | 80-83 | 84-85 | 86-94 | 96-103 | 104-111 | 112-127
填充byte数组的代码
protected byte[] createMessageHeader(byte[] content) { int[] set = new int[128]; set = intToBinary(set, 3, 3); set = intToBinary(set, 0, 5); set = intToBinary(set, 1000, 15); set = intToBinary(set, 200, 23); set = intToBinary(set, 200, 31); set = intToBinary(set, 1294967295, 63); set = intToBinary(set, 5, 71); set = intToBinary(set, 3, 79); set = intToBinary(set, 0, 83); set = intToBinary(set, 0, 85); set = intToBinary(set, 1000, 95); set = intToBinary(set, 200, 103); set = intToBinary(set, 200, 111); set = intToBinary(set, 300, 127); BitSet bitSet = binArrayToBitset(set); byte[] b1 = bitSet.toByteArray(); for(int i = 0; i < b1.length; i++) { b1[i] = (byte) (Integer.reverse(b1[i]) >>> 24); } return b1; } protected int[] intToBinary(int[] binary, int val, int start) { // Number should be positive while (val > 0) { binary[start--] = val % 2; val = val / 2; } return binary; } protected BitSet binArrayToBitset(int[] binArray) { BitSet set = new BitSet(128); for(int i = 0; i < binArray.length; i++) { if(binArray[i] != 0) set.set(i); } return set; } //Convenience method to print binary representation of values protected void toBinString(int[] set) { StringBuilder stringBuilder = new StringBuilder(); for(int i = 0; i < set.length; i++) { if(i % 4 == 0) stringBuilder.append("|"); if(i % 64 == 0) stringBuilder.append("\n"); stringBuilder.append(set[i]); } }
当前提取代码及问题
我尝试了多种提取方法,最近的实现如下:
private int extractBits2(byte[] header, int start, int end) { BitSet bitSet = BitSet.valueOf(header); return (int) bitSet.get(start, end + 1).toLongArray()[0]; }
调用时:
int extracted = extractBits2(header, 6, 15)
返回值是0b00000011_10100000,但预期应该是0b0011 1110 1000(对应整数1000)。我哪里考虑漏了?需要实现正确提取指定位范围的值来做验证。
注意:除了一个timestamp是long类型,其余存储的值都是int类型。
问题原因及修复方案
问题出在字节反转操作和BitSet.valueOf()的比特顺序不匹配:
- 在
createMessageHeader中,你对每个byte执行了Integer.reverse(b1[i]) >>> 24,这会反转单个字节内部的比特顺序(比如原字节00111110会变成01111100)。但BitSet.valueOf(byte[])默认按大端比特顺序解析数组——它将每个字节的最高位对应BitSet中索引较大的位,这和你存储时的反转操作完全冲突,导致比特位对应关系错乱。 - 你的
intToBinary方法是从start位置**从右往左(低位到高位)**填充二进制位,逻辑本身和BitSet的索引规则一致,但字节反转破坏了这个对应关系。
修复后的提取方法
方法1:先还原字节反转再用BitSet解析
private int extractBits(byte[] header, int start, int end) { // 先还原每个字节的比特顺序 byte[] reversedBytes = new byte[header.length]; for (int i = 0; i < header.length; i++) { reversedBytes[i] = (byte) (Integer.reverse(header[i]) >>> 24); } // 用还原后的字节数组创建BitSet BitSet bitSet = BitSet.valueOf(reversedBytes); // 提取指定范围的位并转换为整数 BitSet extractedBits = bitSet.get(start, end + 1); return extractedBits.isEmpty() ? 0 : (int) extractedBits.toLongArray()[0]; }
方法2:手动计算(更高效)
private int extractBitsManual(byte[] header, int start, int end) { int result = 0; int totalBits = end - start + 1; for (int i = 0; i < totalBits; i++) { int bitIndex = start + i; // 计算当前位所在的字节索引 int byteIdx = bitIndex / 8; // 因为原字节被反转过,所以字节内的位索引是7 - (bitIndex % 8) int bitInByte = 7 - (bitIndex % 8); // 提取该位的值并设置到结果的对应位置 int bit = (header[byteIdx] >> bitInByte) & 1; result |= (bit << i); } return result; }
验证
调用extractBits(header,6,15)或extractBitsManual(header,6,15),即可得到预期的整数1000。
内容的提问来源于stack exchange,提问作者millerbill3

