【问题标题】:Java BitSet and byte[] usageJava BitSet 和 byte[] 用法
【发布时间】:2012-12-11 12:43:57
【问题描述】:

我有这个应用程序,我应该大量使用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();

这是故意的还是我做错了什么?谢谢。

【问题讨论】:

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

标签: java bytearray bitset


【解决方案1】:

BitSet有几个问题:

  • 它在输出上提供的字节数组的长度,使用.toByteArray(),取决于设置为 1 的最高位(如果没有设置位,则为 0,如果最后一个位设置为 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();
    }
}

【讨论】:

  • 是的,这真的很烦人。所以我创建了一个扩展 BitSet 类的 CustomBitSet 类,并添加了一个数据字段 (int) 来保存 BitSet 的实际长度,所以现在我可以使用错误的位值开始和结束我的 BitSet 对象。但这不是问题所在。
  • 好吧,通过我的解决方案,您可以两全其美,因为您可以获得可靠的byte[] 来编写。正如我所说,如果您愿意,我可以提供示例代码。例如,我已经这样做了,用于在 DNS 标头中设置标志(标志部分位于两个字节上)。
  • 谢谢!我去看看!
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

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

    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);
    

    如果您的位集相当稀疏(或者即使不是),则仅迭代 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() 方法,然后使用位旋转技巧来反转各个字节中的位:

    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;
    }
    

    【讨论】:

    • 这个应用程序将在智能手机和智能电视上运行,所以你认为只迭代真实位会更有效吗?我的 BitSet 是一半 0 一半 1 等间隔分布。
    • 可能是。请参阅编辑以获取替代方法。您可能想要对两者进行基准测试(以及其他答案中建议的解决方案)。
    猜你喜欢
    • 2016-07-08
    • 2011-09-06
    • 1970-01-01
    • 1970-01-01
    • 2011-11-07
    • 1970-01-01
    • 2016-09-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多