移动 Java 位集

Posted

技术标签:

【中文标题】移动 Java 位集【英文标题】:Shifting a Java BitSet 【发布时间】:2012-02-18 22:59:33 【问题描述】:

我正在使用java.util.BitSet 来存储密集的位向量。

我想实现一个将位右移 1 的操作,类似于整数上的 >>>

是否有一个库函数可以转换BitSets?

如果没有,有没有比下面更好的方法?

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”中没有任何内容。表示thisfromIndex 的位映射到输出中的0 @Mike。看起来它的工作方式类似于String.substring( begin, end )。请注意,在这种情况下,begin1,而不是 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 代替BitSetBigInteger 已经有 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 toLongArrayvalueOf(long[])。 基本上得到long数组,移位longs并从移位数组构造一个新的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 位集的主要内容,如果未能解决你的问题,请参考以下文章

Java 位集示例

如何在 C 中实现位集?

Cuda:XOR 单个位集与位集数组

比较位集的最快方法(位集上的 < 运算符)?

用不同大小的位集替换所有内部位集

可变大小的位集[重复]