我正在寻找在任意位置提取任意长度(0 <=长度<= 16)的(无符号)比特序列的最有效方法。骨架类显示我当前的实现如何基本上处理问题:
public abstract class BitArray {
byte[] bytes = new byte[2048];
int bitGet;
public BitArray() {
}
public void readNextBlock(int initialBitGet, int count) {
// substitute for reading from an input stream
for (int i=(initialBitGet>>3); i<=count; ++i) {
bytes[i] = (byte) i;
}
prepareBitGet(initialBitGet, count);
}
public abstract void prepareBitGet(int initialBitGet, int count);
public abstract int getBits(int count);
static class Version0 extends BitArray {
public void prepareBitGet(int initialBitGet, int count) {
bitGet = initialBitGet;
}
public int getBits(int len) {
// intentionally gives meaningless result
bitGet += len;
return 0;
}
}
static class Version1 extends BitArray {
public void prepareBitGet(int initialBitGet, int count) {
bitGet = initialBitGet - 1;
}
public int getBits(int len) {
int byteIndex = bitGet;
bitGet = byteIndex + len;
int shift = 23 - (byteIndex & 7) - len;
int mask = (1 << len) - 1;
byteIndex >>= 3;
return (((bytes[byteIndex] << 16) |
((bytes[++byteIndex] & 0xFF) << 8) |
(bytes[++byteIndex] & 0xFF)) >> shift) & mask;
}
}
static class Version2 extends BitArray {
static final int[] mask = { 0x0, 0x1, 0x3, 0x7, 0xF, 0x1F, 0x3F, 0x7F, 0xFF,
0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF };
public void prepareBitGet(int initialBitGet, int count) {
bitGet = initialBitGet;
}
public int getBits(int len) {
int offset = bitGet;
bitGet = offset + len;
int byteIndex = offset >> 3; // originally used /8
int bitIndex = offset & 7; // originally used %8
if ((bitIndex + len) > 16) {
return ((bytes[byteIndex] << 16 |
(bytes[byteIndex + 1] & 0xFF) << 8 |
(bytes[byteIndex + 2] & 0xFF)) >> (24 - bitIndex - len)) & mask[len];
} else if ((offset + len) > 8) {
return ((bytes[byteIndex] << 8 |
(bytes[byteIndex + 1] & 0xFF)) >> (16 - bitIndex - len)) & mask[len];
} else {
return (bytes[byteIndex] >> (8 - offset - len)) & mask[len];
}
}
}
static class Version3 extends BitArray {
int[] ints = new int[2048];
public void prepareBitGet(int initialBitGet, int count) {
bitGet = initialBitGet;
int put_i = (initialBitGet >> 3) - 1;
int get_i = put_i;
int buf;
buf = ((bytes[++get_i] & 0xFF) << 16) |
((bytes[++get_i] & 0xFF) << 8) |
(bytes[++get_i] & 0xFF);
do {
buf = (buf << 8) | (bytes[++get_i] & 0xFF);
ints[++put_i] = buf;
} while (get_i < count);
}
public int getBits(int len) {
int bit_idx = bitGet;
bitGet = bit_idx + len;
int shift = 32 - (bit_idx & 7) - len;
int mask = (1 << len) - 1;
int int_idx = bit_idx >> 3;
return (ints[int_idx] >> shift) & mask;
}
}
static class Version4 extends BitArray {
int[] ints = new int[1024];
public void prepareBitGet(int initialBitGet, int count) {
bitGet = initialBitGet;
int g = initialBitGet >> 3;
int p = (initialBitGet >> 4) - 1;
final byte[] b = bytes;
int t = (b[g] << 8) | (b[++g] & 0xFF);
final int[] i = ints;
do {
i[++p] = (t = (t << 16) | ((b[++g] & 0xFF) <<8) | (b[++g] & 0xFF));
} while (g < count);
}
public int getBits(final int len) {
final int i;
bitGet = (i = bitGet) + len;
return (ints[i >> 4] >> (32 - len - (i & 15))) & ((1 << len) - 1);
}
}
public void benchmark(String label) {
int checksum = 0;
readNextBlock(32, 1927);
long time = System.nanoTime();
for (int pass=1<<18; pass>0; --pass) {
prepareBitGet(32, 1927);
for (int i=2047; i>=0; --i) {
checksum += getBits(i & 15);
}
}
time = System.nanoTime() - time;
System.out.println(label+" took "+Math.round(time/1E6D)+" ms, checksum="+checksum);
try { // avoid having the console interfere with our next measurement
Thread.sleep(369);
} catch (InterruptedException e) {}
}
public static void main(String[] argv) {
BitArray test;
// for the sake of getting a little less influence from the OS for stable measurement
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
while (true) {
test = new Version0();
test.benchmark("no implementaion");
test = new Version1();
test.benchmark("Durandal's (original)");
test = new Version2();
test.benchmark("blitzpasta's (adapted)");
test = new Version3();
test.benchmark("MSN's (posted)");
test = new Version4();
test.benchmark("MSN's (half-buffer modification)");
System.out.println("--- next pass ---");
}
}
}
这有效,但我正在寻找更有效的解决方案(性能明智)。保证字节数组相对较小,在几个字节之间,最大为~1800字节。在每次调用read方法之间,只读取一次(完全)数组。 getBits()中不需要任何错误检查,例如超出数组等。
上面我的初步问题似乎不够明确。 N比特的“比特序列”形成N比特的整数,并且我需要以最小的开销提取那些整数。我没有使用字符串,因为这些值既可以用作查找索引,也可以直接输入到某些计算中。基本上,上面显示的骨架是一个真正的类,getBits()签名显示了其余代码如何与它交互。
将示例代码扩展到微基准测试中,包括blitzpasta的解决方案(修复丢失的字节屏蔽)。在我的旧AMD盒子上,结果为~11400ms vs~38000ms。仅供参考:其分裂和模数操作会破坏性能。如果将 / 8 替换为&gt;&gt; 3 ,将%8 替换为&amp; 7 ,则两种解决方案均为非常接近(jdk1.7.0ea104)。
对于如何以及如何工作似乎有点混乱。示例代码的第一个原始帖子包含一个read()方法,用于指示填充字节缓冲区的位置和时间。当代码变成微基座时,这就丢失了。我重新介绍它,使它更清晰一点。 我们的想法是通过添加另一个需要实现getBits()和prepareBitGet()的BitArray子类来击败所有现有版本,后者可能是空的。 不要更改基准测试以使您的解决方案具有优势,对所有现有解决方案也可以这样做,这使得这完全没有实际优化! (真!!)
我添加了一个Version0,它只会增加bitGet状态。它总是返回0,以便大致了解基准开销有多大。它仅用于比较。
此外,还增加了对MSN想法的改编(版本3)。为了使所有竞争对手保持公平和可比性,字节数组填充现在是基准测试的一部分,以及准备步骤(见上文)。最初MSN的解决方案做得不好,准备int []缓冲区有很多开销。我冒昧地优化了一步,把它变成了一个激烈的竞争对手:) 您可能还会发现我对您的代码进行了一些解构。你的getBit()可以压缩成3线,可能会削减一到两个百分点。我故意这样做是为了保持代码的可读性,因为其他版本也没有尽可能精简(再次为了提高可读性)。
结论(上面的代码示例更新,包括基于所有适用的贡献的版本)。在我的旧AMD盒子(Sun JRE 1.6.0_21)上,它们出现在:
V0没有实施 5384 ms
V1 Durandal(原作) 10283 ms
V2 blitzpasta(改编自) 12212 ms
V3 MSN(已发布) 11030 ms
V4 MSN(半缓冲区修改) 9700 ms
注意:在此基准测试中,每次调用getBits()时都会获取7.5位的平均值,并且每个位只读取一次。由于V3 / V4必须支付较高的初始化成本,因此它们倾向于显示更好的运行时行为,具有更多,更短的提取(并且因此越接近平均提取大小的最大值16)。不过,在所有场景中,V4仍略微领先于其他所有场景。 在实际应用中,必须考虑缓存争用,因为V3 / v4所需的额外空间可能会将缓存未命中增加到V0将是更好选择的点。如果要遍历数组不止一次,那么V4应该受到青睐,因为它比其他所有数据都要快,并且在第一次通过后,昂贵的初始化就会分摊。
答案 0 :(得分:3)
如果您只想将无符号位序列作为int。
static final int[] lookup = {0x0, 0x1, 0x3, 0x7, 0xF, 0x1F, 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF };
/*
* bytes: byte array, with the bits indexed from 0 (MSB) to (bytes.length * 8 - 1) (LSB)
* offset: index of the MSB of the bit sequence.
* len: length of bit sequence, must from range [0,16].
* Not checked for overflow
*/
static int getBitSeqAsInt(byte[] bytes, int offset, int len){
int byteIndex = offset / 8;
int bitIndex = offset % 8;
int val;
if ((bitIndex + len) > 16) {
val = ((bytes[byteIndex] << 16 | bytes[byteIndex + 1] << 8 | bytes[byteIndex + 2]) >> (24 - bitIndex - len)) & lookup[len];
} else if ((offset + len) > 8) {
val = ((bytes[byteIndex] << 8 | bytes[byteIndex + 1]) >> (16 - bitIndex - len)) & lookup[len];
} else {
val = (bytes[byteIndex] >> (8 - offset - len)) & lookup[len];
}
return val;
}
如果你想把它作为一个字符串(修改Margus的答案)。
static String getBitSequence(byte[] bytes, int offset, int len){
int byteIndex = offset / 8;
int bitIndex = offset % 8;
int count = 0;
StringBuilder result = new StringBuilder();
outer:
for(int i = byteIndex; i < bytes.length; ++i) {
for(int j = (1 << (7 - bitIndex)); j > 0; j >>= 1) {
if(count == len) {
break outer;
}
if((bytes[byteIndex] & j) == 0) {
result.append('0');
} else {
result.append('1');
}
++count;
}
bitIndex = 0;
}
return result.toString();
}
答案 1 :(得分:2)
那么,根据你想要的时间与内存跷跷板相比,您可以在每个16位偏移处分配每32位的边表,然后根据16位偏移量:
byte[] bytes = new byte[2048];
int bitGet;
unsigned int dwords[] = new unsigned int[2046];
public BitArray() {
for (int i=0; i<bytes.length; ++i) {
bytes[i] = (byte) i;
}
for (int i= 0; i<dwords.length; ++i) {
dwords[i]=
(bytes[i ] << 24) |
(bytes[i + 1] << 16) |
(bytes[i + 2] << 8) |
(bytes[i + 3]);
}
}
int getBits(int len)
{
int offset= bitGet;
int offset_index= offset>>4;
int offset_offset= offset & 15;
return (dwords[offset_index] >> offset_offset) & ((1 << len) - 1);
}
您可以避免分支(以内存占用量翻两番为代价)。并且正在查看面具真的比(1&lt;&lt;&lt;&lt; len) - 1?
快得多答案 2 :(得分:1)
只是想知道为什么不能使用 java.util.BitSet;
基本上你可以做的是将整个数据读作byte[]
,将其转换为string
格式的二进制文件,并使用.substring()
之类的字符串实用程序来完成工作。这也可以bit sequences > 16
。
假设您有3个字节:1, 2, 3
并且您想要从第5位到第16位提取位序列。
数字二进制
1 00000001
2 00000010
3 00000011
代码示例:
public static String getRealBinary(byte[] input){
StringBuilder sb = new StringBuilder();
for (byte c : input) {
for (int n = 128; n > 0; n >>= 1){
if ((c & n) == 0)
sb.append('0');
else sb.append('1');
}
}
return sb.toString();
}
public static void main(String[] args) {
byte bytes[] = new byte[]{1,2,3};
String sbytes = getRealBinary(bytes);
System.out.println(sbytes);
System.out.println(sbytes.substring(5,16));
}
<强>输出:强>
000000010000001000000011
00100000010
<强>速度强>
我做了 1m 次的测试,并且在我的电脑上它花了 0.995s ,所以它的速度相当快:
自行重复测试的代码:
public static void main(String[] args) {
Random r = new Random();
byte bytes[] = new byte[4];
long start, time, total=0;
for (int i = 0; i < 1000000; i++) {
r.nextBytes(bytes);
start = System.currentTimeMillis();
getRealBinary(bytes).substring(5,16);
time = System.currentTimeMillis() - start;
total+=time;
}
System.out.println("It took " +total + "ms");
}
答案 3 :(得分:0)
您最多需要16位,取自字节数组。 16位最多可以跨越3个字节。 这是一个可能的解决方案:
int GetBits(int bit_index, int bit_length) {
int byte_offset = bit_index >> 3;
return ((((((byte_array[byte_offset]<<8)
+byte_array[byte_offset+1])<<8)
+byte_array[byte_offset+2]))
>>(24-(bit_index&7)+bit_length))))
&((1<<bit_length)-1);
}
[未测试]
如果你经常调用它,你应该预先计算3个连接字节的24位值,并将它们存储到int数组中。
我会观察到如果你在x86上用C编码,你甚至不需要预先计算24位数组;只需在欲望的偏移处访问by te数组作为32位值。 x86将完成未对齐的提取。 [评论者注意到,endianess将其解决了,所以这不是一个答案,好的,24位版本。]
答案 4 :(得分:0)
由于Java 7 BitSet
采用toLongArray
方法,我认为该方法将完全满足问题的要求:
int subBits = (int) bitSet.get(lowBit, highBit).toLongArray()[0];
这样做的优点是它适用于大于整数或长整数的序列。它的性能缺点是必须分配一个新的BitSet
对象,以及一个新的数组对象来保存结果。
看看这与基准测试中的其他方法相比,真的很有趣。