问题 Java BitSet和byte []用法


我有这个应用程序,我应该使用 BitSet 大量分类并逐位写入文件。我知道我不能写位到文件,所以首先我转换 BitSet 对象到字节数组并写为字节数组。但问题是从那以后 BitSet 从中索引的类 right to left,当我转换 BitSet 对象到字节数组并写入文件,它向后写。

例如,这是我的BitSet对象:

10100100

和BitSet.get(0)给出false,BitSet.get(7)给出true。我想把它写成文件,如:

00100101

所以第一位为0,最后一位为1。

我的转换方法:

public static byte[] toByteArray(BitSet bits) 
{
    byte[] bytes = new byte[(bits.length() + 7) / 8];       
    for (int i = 0; i < bits.length(); i++) {
        if (bits.get(i)) {
            bytes[bytes.length - i / 8 - 1] |= 1 << (i % 8);
        }
    }
    return bytes;
}

我的写法:

    FileOutputStream fos = new FileOutputStream(filePath);
    fos.write(BitOperations.toByteArray(cBitSet));
    fos.close();

这是打算像这样还是我做错了什么?谢谢。


10853
2017-12-25 16:51


起源

你如何使用这个文件?你以后在java中读它来恢复BitSet吗? - tcb
我把这个文件作为字节数组读取,并在需要时将其转换为BitSet,但是对于外部二进制查看器,它也会向后看。 - gmnnn


答案:


BitSet实现了Serializable。如果您只需要能够在Java中恢复BitSet,并且不需要在文件中检查其状态,那么您应该告诉它将自己保存到文件中。

如果您希望将其写入包含其他非序列化数据的文件,则可以将其写入ByteArrayOutputStream并从中检索byte []。但是,您可能会在直接写入文件时获得更好的性能。


6
2017-12-25 17:21





BitSet 有几个问题:

  • 使用时在输出上提供的字节数组的长度 .toByteArray(),取决于最高位设置为1(如果没有设置,则为0;如果最后一位设置为<8则为1,如果<16则为2等 - 实质上, indexOf(highestBitSet) + 7) / 8);
  • 因此,您不能依赖它来计算固定长度的位掩码。

考虑使用包装器 ByteBuffer 代替。示例代码如下。

注意:这使用“静态工厂方法”进行构造,因此您需要使用其中任何一个 BitFlags.withByteLength() 要么 BitFlags.withBitLength() 创建一个新实例。当然,您可以为此设计自己的方法,或者只是将构造函数设为公共。要获取底层数组,请调用 .toByteArray()

public final class BitFlags
{
    private final int nrBytes;
    private final ByteBuffer buf;

    private BitFlags(final int nrBytes)
    {
        if (nrBytes < 1)
            throw new IllegalArgumentException("need at least one byte");
        this.nrBytes = nrBytes;
        buf = ByteBuffer.allocate(nrBytes);
    }

    public static BitFlags withByteLength(final int nrBytes)
    {
        return new BitFlags(nrBytes);
    }

    public static BitFlags withBitLength(final int nrBits)
    {
        return new BitFlags((nrBits - 1) / 8 + 1);
    }

    public void setBit(final int bitOffset)
    {
        if (bitOffset < 0)
            throw new IllegalArgumentException();

        final int byteToSet = bitOffset / 8;
        if (byteToSet > nrBytes)
            throw new IllegalArgumentException();

        final int offset = bitOffset % 8;
        byte b = buf.get(byteToSet);
        b |= 1 << offset;
        buf.put(byteToSet, b);
    }

    public void unsetBit(final int bitOffset)
    {
        if (bitOffset < 0)
            throw new IllegalArgumentException();

        final int byteToSet = bitOffset / 8;
        if (byteToSet > nrBytes)
            throw new IllegalArgumentException();

        final int offset = bitOffset % 8;
        byte b = buf.get(byteToSet);
        b &= ~(1 << offset);
        buf.put(byteToSet, b);
    }

    public byte[] toByteArray()
    {
        return buf.array();
    }
}

6
2017-12-25 17:09



是的,这真的很烦人。所以我创建了一个扩展BitSet类的CustomBitSet类,并添加了一个数据字段(int)来保存BitSet的实际长度,所以现在我可以使用false位值来启动和结束我的BitSet对象。但这不是问题。 - gmnnn
好吧,凭借我的解决方案,您将获得两全其美,因为您可以获得可靠的 byte[] 来写。正如我所说,如果您愿意,我可以提供示例代码。我已经这样做了,例如,在DNS标头中设置标志(标志部分在两个字节上)。 - fge
我想看看你的代码,谢谢。 - gmnnn
@gmnnn:你有;) - fge
谢谢!我会看看! - gmnnn


这看起来很合理。它不会很快,但它应该工作。如果您希望它以相反的顺序写出位,只需反转索引和移位:

byte[] bytes = new byte[(bits.length() + 7) / 8];       
for (int i = 0; i < bits.length(); i++) {
    if (bits.get(i)) {
        bytes[i / 8] |= 1 << (7 - i % 8);
    }
}

甚至:

        bytes[i / 8] |= 128 >> (i % 8);

如果你的bitset相当稀疏(或者即使它不是),只能迭代1位可能更有效:

byte[] bytes = new byte[(bits.length() + 7) / 8];
for ( int i = bits.nextSetBit(0); i >= 0; i = bits.nextSetBit(i+1) ) {
    bytes[i / 8] |= 128 >> (i % 8);
}

如果您需要更高速度的密集位集,那么 可以 尝试使用该标准 BitSet.toByteArray() 方法然后使用bit-twiddling技巧来反转单个字节中的位:

byte[] bytes = bits.toByteArray();
for ( int i = 0; i < bytes.length; i++ ) {
    byte b = bytes[i];
    b = ((b & 0x0F) << 4) | ((b & 0xF0) >> 4);
    b = ((b & 0x33) << 2) | ((b & 0xCC) >> 2);
    b = ((b & 0x55) << 1) | ((b & 0xAA) >> 1);
    bytes[i] = b;
}

0
2017-12-25 17:10



这个应用程序将在智能手机和智能电视上运行,那么您认为只有迭代真正的位才会更有效吗?我的BitSet是等间隔分布的半个0的一半。 - gmnnn
它可能是。请参阅编辑以获取其他方法。您可能希望对两者进行基准测试(以及其他答案中建议的解决方案)。 - Ilmari Karonen