移动 Java 位集
Posted
技术标签:
【中文标题】移动 Java 位集【英文标题】:Shifting a Java BitSet 【发布时间】:2012-02-18 22:59:33 【问题描述】:我正在使用java.util.BitSet
来存储密集的位向量。
我想实现一个将位右移 1 的操作,类似于整数上的 >>>
。
是否有一个库函数可以转换BitSet
s?
如果没有,有没有比下面更好的方法?
public static void logicalRightShift(BitSet bs)
for (int i = 0; (i = bs.nextSetBit(i)) >= 0;)
// i is the first bit in a run of set bits.
// Set any bit to the left of the run.
if (i != 0) bs.set(i - 1);
// Now i is the index of the bit after the end of the run.
i = bs.nextClearBit(i); // nextClearBit never returns -1.
// Clear the last bit of the run.
bs.clear(i - 1);
// 0000111100000...
// a b
// i starts off the loop at a, and ends the loop at b.
// The mutations change the run to
// 0001111000000...
【问题讨论】:
等等,这是左逻辑移位,不是右逻辑移位。对吗? 我认为 BitSet 索引零处的位是最左边的。与表示整数的位串不同,没有明确的最高或最低有效位,因此方向的标签是任意的。 【参考方案1】:这应该可以解决问题:
BitSet shifted = bs.get(1, bs.length());
它会给你一个等于原始的位集,但没有最低位。
编辑:
将其推广到n
位,
BitSet shifted = bs.get(n, Math.max(n, bs.length()));
【讨论】:
get
上的 [文档](docs.oracle.com/javase/7/docs/api/java/util/BitSet.html#get(int, int)) 让我感到困惑。 “返回由来自 fromIndex(包括)到 toIndex(不包括)的 BitSet 中的位组成的新 BitSet”中没有任何内容。表示this
中fromIndex
的位映射到输出中的0
。
@Mike。看起来它的工作方式类似于String.substring( begin, end )
。请注意,在这种情况下,begin
是 1
,而不是 0
。
@AlexanderPogrebnyak,您是凭经验确定的,还是有实际文档可以保证在所有实现中都做到这一点?
@Mike。这就是文档所说的。至少对我来说:)。
@AlexanderPogrebnyak,我认为我引用的 Javadoc 可以解释为处理 x = bs.get(1, bs.cardinality()+1)
和 x = (BitSet) bs.clone(); x.clear(0)
【参考方案2】:
可能更有效的替代方法是使用底层 long[]。
使用bitset.toLongArray()
获取基础数据。相应地移动这些多头,然后通过BitSet.valueOf(long[])
创建一个新的 BitSet 您必须非常小心地移动底层多头,因为您必须在下一个多头时取出低位并将其转移到高位数组。
这应该让您可以使用处理器原生的位移操作来一次移动 64 位,而不是单独迭代每个位。
编辑:基于 Louis Wasserman 的评论。这仅在 Java 1.7 API 中可用。写的时候没想到。
【讨论】:
这不需要我手动捕捉低位并将其传播到前一个长的末尾吗?这会执行两个数组副本吗? @MikeSamuel - 两者都是。但是,我相信它仍然会更快。不确定这对您的问题是否重要。看看 Philipp 的建议,我认为这将是最简单的,而且可能是最快的。【参考方案3】:请在 BitSet 被“左移”的地方找到这个代码块
/**
* Shift the BitSet to left.<br>
* For example : 0b10010 (=18) => 0b100100 (=36) (equivalent to multiplicate by 2)
* @param bitSet
* @return shifted bitSet
*/
public static BitSet leftShiftBitSet(BitSet bitSet)
final long maskOfCarry = 0x8000000000000000L;
long[] aLong = bitSet.toLongArray();
boolean carry = false;
for (int i = 0; i < aLong.length; ++i)
if (carry)
carry = ((aLong[i] & maskOfCarry) != 0);
aLong[i] <<= 1;
++aLong[i];
else
carry = ((aLong[i] & maskOfCarry) != 0);
aLong[i] <<= 1;
if (carry)
long[] tmp = new long[aLong.length + 1];
System.arraycopy(aLong, 0, tmp, 0, aLong.length);
++tmp[aLong.length];
aLong = tmp;
return BitSet.valueOf(aLong);
【讨论】:
【参考方案4】:您可以使用BigInteger
代替BitSet
。 BigInteger
已经有 ShiftRight 和 ShiftLeft。
【讨论】:
您的答案已被标记为不是答案,“不是最有效的方法”很有趣,但您应该尝试使用 BigInteger 类展示一些示例代码如何实现这一点......来自评论 作者正确地指出,内置移位运算符提供了使用 BI 而不是 BS 的充分理由。当然可以,`BigInteger bi = new BigInteger(bs.toByteArray()); bi.shiftLeft(12); bs = BitSet.valueOf(bi.toByteArray());` 如果绝对需要。【参考方案5】:这些函数分别模仿 >> 运算符。
/**
* Shifts a BitSet n digits to the left. For example, 0b0110101 with n=2 becomes 0b10101.
*
* @param bits
* @param n the shift distance.
* @return
*/
public static BitSet shiftLeft(BitSet bits, int n)
if (n < 0)
throw new IllegalArgumentException("'n' must be >= 0");
if (n >= 64)
throw new IllegalArgumentException("'n' must be < 64");
long[] words = bits.toLongArray();
// Do the shift
for (int i = 0; i < words.length - 1; i++)
words[i] >>>= n; // Shift current word
words[i] |= words[i + 1] << (64 - n); // Do the carry
words[words.length - 1] >>>= n; // shift [words.length-1] separately, since no carry
return BitSet.valueOf(words);
/**
* Shifts a BitSet n digits to the right. For example, 0b0110101 with n=2 becomes 0b000110101.
*
* @param bits
* @param n the shift distance.
* @return
*/
public static BitSet shiftRight(BitSet bits, int n)
if (n < 0)
throw new IllegalArgumentException("'n' must be >= 0");
if (n >= 64)
throw new IllegalArgumentException("'n' must be < 64");
long[] words = bits.toLongArray();
// Expand array if there will be carry bits
if (words[words.length - 1] >>> (64 - n) > 0)
long[] tmp = new long[words.length + 1];
System.arraycopy(words, 0, tmp, 0, words.length);
words = tmp;
// Do the shift
for (int i = words.length - 1; i > 0; i--)
words[i] <<= n; // Shift current word
words[i] |= words[i - 1] >>> (64 - n); // Do the carry
words[0] <<= n; // shift [0] separately, since no carry
return BitSet.valueOf(words);
【讨论】:
谢谢。 n 上的 64 边界似乎是任意的,但可以通过首先将单词复制到一个新的数组中来放宽这个限制,该数组在适当的方向上移动 (n / 64)。 这个问题很老了,但我仍然想对此发表评论。任何限制移位 n long,或者使用具有左右移位功能的BigInteger
。如果坚持BitSet
,应考虑在将位值放入BitSet
之前移动bitIndex
。
1.我认为函数名称是相反的。 2. 这不适用于大于 64 的位。 3. 我所做的(而且速度很慢)是将位集转换为大于 64 位的大整数,然后移动位集数。【参考方案6】:
您可以查看 BitSet toLongArray
和 valueOf(long[])
。
基本上得到long
数组,移位long
s并从移位数组构造一个新的BitSet
。
【讨论】:
【参考方案7】:为了获得更好的性能,您可以扩展 java.util.BitSet 实现并避免不必要的数组复制。这是实现(我基本上重用了 Jeff Piersol 实现):
package first.specific.structure;
import java.lang.reflect.Field;
import java.util.BitSet;
public class BitSetMut extends BitSet
private long[] words;
private static Field wordsField;
static
try
wordsField = BitSet.class.getDeclaredField("words");
wordsField.setAccessible(true);
catch (NoSuchFieldException e)
throw new IllegalStateException(e);
public BitSetMut(final int regLength)
super(regLength);
try
words = (long[]) wordsField.get(this);
catch (IllegalAccessException e)
throw new IllegalStateException(e);
public void shiftRight(int n)
if (n < 0)
throw new IllegalArgumentException("'n' must be >= 0");
if (n >= 64)
throw new IllegalArgumentException("'n' must be < 64");
if (words.length > 0)
ensureCapacity(n);
// Do the shift
for (int i = words.length - 1; i > 0; i--)
words[i] <<= n; // Shift current word
words[i] |= words[i - 1] >>> (64 - n); // Do the carry
words[0] <<= n; // shift [0] separately, since no carry
// recalculateWordInUse() is unnecessary
private void ensureCapacity(final int n)
if (words[words.length - 1] >>> n > 0)
long[] tmp = new long[words.length + 3];
System.arraycopy(words, 0, tmp, 0, words.length);
words = tmp;
try
wordsField.set(this, tmp);
catch (IllegalAccessException e)
throw new IllegalStateException(e);
【讨论】:
这似乎很脆弱。它依赖于一个私有字段,它不仅具有特定的类型和语义,而且还具有特定的名称。还有,ensureCapacity
不会丢失单词和超类私有字段之间的别名关系。虽然它确实很快就会失败,所以脆性可能是可以控制的。你会得到什么样的性能加速来换取脆弱性?
@Mike 你在 ensureCapacity(n) 方法上是绝对正确的,这是我的错误,所以我只是修复了它。我在一些计算量大的电信算法(如scrambling)中使用了这个BitSetMut 实现作为线性反馈移位寄存器。 BitSetMut 提供了避免不必要的数组复制和垃圾生成的机会,因此总体延迟要低得多。与使用 BitSet 和静态 shiftRight 方法的 Scrambler 相比,使用 BitSetMut 的 Scrambler 实现快 2 倍。【参考方案8】:
使用java SE8,可以实现更简洁的方式:
BitSet b = new BitSet();
b.set(1, 3);
BitSet shifted = BitSet.valueOf(Arrays.stream(
b.toLongArray()).map(v -> v << 1).toArray());
我试图弄清楚如何使用 LongBuffer 来做到这一点,但还没有完全发挥作用。希望熟悉低级编程的人可以指出一个解决方案。
提前致谢!!!
【讨论】:
以上是关于移动 Java 位集的主要内容,如果未能解决你的问题,请参考以下文章