最快的方法
BitBank对这个问题的回答比本回答下面的两种方法快大约两倍。它借鉴了BitBank的答案,并通过使用位操作来重复关闭最低有效位,而不是将一位右移并跟踪移位量,在我的机器上使其比BitBank的方法快73%(比问题的方法快9倍)。
private static final byte[] bitPositions(long n) {
final byte[] result = new byte[Long.bitCount(n)];
for (int i = 0; n != 0L; i++) {
result[i] = (byte) ((byte) Long.numberOfTrailingZeros(n) + 1);
n &= n - 1L;
}
return result;
}
BitBank答案的改进
- 不需要跟踪我们跳过了多少位。
- 快速将最后一个1位变为0位。
- 双重转换为
byte
略微加快了速度。我认为这是因为它允许使用byte
大小而不是int
大小的算术运算。
手动合并
正如Durandal在问题的评论中指出的那样,您可以交换以下内容:
for (int bitPosition : bitPositions(n)) {
}
对于一个跳过方法调用并使用以下方式的样式:
long temp = n;
while (temp != 0L) {
int bitPosition = Long.numberOfTrailingZeros(temp) + 1;
temp &= temp - 1L;
}
融合的好处
- 不需要浪费时间调用方法。
- 无需创建或垃圾回收数组,节省时间和内存。
- 位位置可以保留在非常快速的CPU寄存器中,整个使用过程中都不需要将其写入RAM中的数组(这样会慢得多),然后再从RAM中读取它。
融合的缺点
最慢的方法
这是一种方法,它产生相同的结果(只是在 byte[]
中而不是 List<Integer>
),速度大约快两倍:
private static final byte[] bitPositions(long n) {
final byte[] result = new byte[Long.bitCount(n)];
int i = 0;
for (byte bit = 1; n != 0L; bit++) {
if ((n & 1L) != 0) result[i++] = bit;
n >>>= 1;
}
return result;
}
我建议将
for
循环中的
byte bit = 1
更改为
byte bit = 0
,以切换到传统的位位置编号方法,从零开始而不是从一开始。
改进
- 使用
Long.bitCount(n)
预先计算所需容量(使用处理器的非常快速的“popcount”指令)可以加快方法的速度。您可以通过使用new ArrayList<>(Long.bitCount(n))
来更改此设置。
- 使用
ArrayList<Integer>
比使用byte[]
慢,因为:
- 必须浪费时间查找低值(
-127
到128
)的Integer
值从Integer
缓存放入ArrayList
中。
- 使用存储在结果
List<Integer>
中的int
时,必须浪费时间,因为您必须从List<Integer>
检索Integer
,然后检索int
。
byte[]
使用大约1/4(32位系统)或1/8(64位系统)的ArrayList<Integer>
内存,因为byte
比指向Integer
的指针小那么多。
比最慢的方法稍微快一点,但更难看
正如另一个人已删除的答案所建议的那样,在我的机器上展开循环可以进一步加速(请检查在您的机器上是否也是如此):
private static final byte[] bitPositions(final long n) {
final byte[] result = new byte[Long.bitCount(n)];
int i = 0;
if ((n & 1L) != 0L) result[i++] = 1;
if ((n & 2L) != 0L) result[i++] = 2;
if ((n & 4L) != 0L) result[i++] = 3;
if ((n & 8L) != 0L) result[i++] = 4;
if ((n & 16L) != 0L) result[i++] = 5;
if ((n & 32L) != 0L) result[i++] = 6;
if ((n & 64L) != 0L) result[i++] = 7;
if ((n & 128L) != 0L) result[i++] = 8;
if ((n & 256L) != 0L) result[i++] = 9;
if ((n & 512L) != 0L) result[i++] = 10;
if ((n & 1024L) != 0L) result[i++] = 11;
if ((n & 2048L) != 0L) result[i++] = 12;
if ((n & 4096L) != 0L) result[i++] = 13;
if ((n & 8192L) != 0L) result[i++] = 14;
if ((n & 16384L) != 0L) result[i++] = 15;
if ((n & 32768L) != 0L) result[i++] = 16;
if ((n & 65536L) != 0L) result[i++] = 17;
if ((n & 131072L) != 0L) result[i++] = 18;
if ((n & 262144L) != 0L) result[i++] = 19;
if ((n & 524288L) != 0L) result[i++] = 20;
if ((n & 1048576L) != 0L) result[i++] = 21;
if ((n & 2097152L) != 0L) result[i++] = 22;
if ((n & 4194304L) != 0L) result[i++] = 23;
if ((n & 8388608L) != 0L) result[i++] = 24;
if ((n & 16777216L) != 0L) result[i++] = 25;
if ((n & 33554432L) != 0L) result[i++] = 26;
if ((n & 67108864L) != 0L) result[i++] = 27;
if ((n & 134217728L) != 0L) result[i++] = 28;
if ((n & 268435456L) != 0L) result[i++] = 29;
if ((n & 536870912L) != 0L) result[i++] = 30;
if ((n & 1073741824L) != 0L) result[i++] = 31;
if ((n & 2147483648L) != 0L) result[i++] = 32;
if ((n & 4294967296L) != 0L) result[i++] = 33;
if ((n & 8589934592L) != 0L) result[i++] = 34;
if ((n & 17179869184L) != 0L) result[i++] = 35;
if ((n & 34359738368L) != 0L) result[i++] = 36;
if ((n & 68719476736L) != 0L) result[i++] = 37;
if ((n & 137438953472L) != 0L) result[i++] = 38;
if ((n & 274877906944L) != 0L) result[i++] = 39;
if ((n & 549755813888L) != 0L) result[i++] = 40;
if ((n & 1099511627776L) != 0L) result[i++] = 41;
if ((n & 2199023255552L) != 0L) result[i++] = 42;
if ((n & 4398046511104L) != 0L) result[i++] = 43;
if ((n & 8796093022208L) != 0L) result[i++] = 44;
if ((n & 17592186044416L) != 0L) result[i++] = 45;
if ((n & 35184372088832L) != 0L) result[i++] = 46;
if ((n & 70368744177664L) != 0L) result[i++] = 47;
if ((n & 140737488355328L) != 0L) result[i++] = 48;
if ((n & 281474976710656L) != 0L) result[i++] = 49;
if ((n & 562949953421312L) != 0L) result[i++] = 50;
if ((n & 1125899906842624L) != 0L) result[i++] = 51;
if ((n & 2251799813685248L) != 0L) result[i++] = 52;
if ((n & 4503599627370496L) != 0L) result[i++] = 53;
if ((n & 9007199254740992L) != 0L) result[i++] = 54;
if ((n & 18014398509481984L) != 0L) result[i++] = 55;
if ((n & 36028797018963968L) != 0L) result[i++] = 56;
if ((n & 72057594037927936L) != 0L) result[i++] = 57;
if ((n & 144115188075855872L) != 0L) result[i++] = 58;
if ((n & 288230376151711744L) != 0L) result[i++] = 59;
if ((n & 576460752303423488L) != 0L) result[i++] = 60;
if ((n & 1152921504606846976L) != 0L) result[i++] = 61;
if ((n & 2305843009213693952L) != 0L) result[i++] = 62;
if ((n & 4611686018427387904L) != 0L) result[i++] = 63;
if ((n & -9223372036854775808L) != 0L) result[i++] = 64;
return result;
}
您也可以将其更改为从零开始计算位位置,而不是从一开始计算。
改进
- 避免需要反复对数字执行
>>>
。
- 避免需要反复对位位置执行
++
。
- 避免需要检查数字是否已达到零。
- 避免了一些分支预测错误。
>>>
是逻辑移位,它会在另一端插入零,并最终用零填充每个位,而不是算术移位。 - Chai T. Rex