如何在Java中将布尔数组转换为二进制,反之亦然?

4
什么是在Java中将布尔数组输出到(和输入自)文件的最有效方法?我本来想使用一个字符串,其中每个字符都是't'或'f',然后我想,为什么不节省八倍的空间?
注:实际上我不知道哪种方法更好,我只选择了Peter's,因为我理解它。感谢两位回答者!

一般的结果是将布尔数组转换为字节数组(其中1位是一个布尔值),并将其输出到文件中。问题在于如何高效地完成这样的操作。您只需要将位存储在文件中,还是需要特定的文件格式? - Maarten Bodewes
嗨@owlstead,感谢您的回复。没有特定的文件格式。文件将首先指示布尔数组的大小,然后读取算法将使用此信息来知道文件中还有多少个字节表示该数组。 - flea whale
有趣的是,这听起来像是一个文件格式。布尔值的数量有没有最大的大小限制? - Maarten Bodewes
@owlstead 我以为你指的是已经建立好的文件格式。该数组的最大大小为10K(一万个布尔值)。 - flea whale
2个回答

7

假设你有一个boolean[]数组

boolean[] ar = {true,false,false,true,false,true,true,true,false,true,false,false,false,true,tr‌​ue};

如果您想将其写入磁盘,而且对于它在内存中的实现方式并不关心。

public static void main(String... args) throws IOException {
    boolean[] ar = {true, false, false, true, false, true, true, true, false, true, false, false, false, true, true};

    FileOutputStream out = new FileOutputStream("test.dat");
    writeBooleans(out, ar);
    out.close();

    FileInputStream in = new FileInputStream("test.dat");
    boolean[] ar2 = new boolean[ar.length]; 
    readBooleans(in, ar2);
    in.close();

    System.out.println(Arrays.toString(ar));
    System.out.println(Arrays.toString(ar2));
    System.out.println("The file size was "+new File("test.dat").length()+" bytes.");
}

private static void writeBooleans(OutputStream out, boolean[] ar) throws IOException {
    for (int i = 0; i < ar.length; i += 8) {
        int b = 0;
        for (int j = Math.min(i + 7, ar.length-1); j >= i; j--) {
            b = (b << 1) | (ar[j] ? 1 : 0);
        }
        out.write(b);
    }
}

private static void readBooleans(InputStream in, boolean[] ar) throws IOException {
    for (int i = 0; i < ar.length; i += 8) {
        int b = in.read();
        if (b < 0) throw new EOFException();
        for (int j = i; j < i + 8 && j < ar.length; j++) {
            ar[j] = (b & 1) != 0;
            b >>>= 1;
        }
    }
}

打印
[true, false, false, true, false, true, true, true, false, true, false, false, false, true, true]
[true, false, false, true, false, true, true, true, false, true, false, false, false, true, true]
The file size was 2 bytes.

但是如果我查看文件的实际大小

$ ls -l test.dat
-rw-rw-r-- 1 peter peter 2 2012-02-19 14:04 test.dat
$ du -h test.dat 
4.0K    test.dat

它说长度为2个字节,但实际使用的磁盘空间却是4 KB。

注意:您花费的1分钟时间价值大约相当于80 MB的SSD(昂贵的磁盘,HDD更多)。因此,如果您认为使用它不能至少节省80 MB,则可能会浪费您的时间。;)


您可以使用BitSet,每个字符都是16位,可以减少16倍的空间。


每个字符都是16位吗?不是在磁盘上,这取决于编码方式 - 比如说,UTF-8只需要8位来表示tf。尽管如此,我仍然认为位掩码的概念是提高空间效率的途径:长度=O(n)+1,其中n是布尔数组的长度。 - user268396
是的,我个人讨厌BitSet,因为它不提供任何转换方法。EnumSet也一样。我已经为两者编写了一些库函数,我应该真正将它们发送给Joshua Bloch。 - Maarten Bodewes
@owlstead 是的,但如果你将其序列化,它就可以写入磁盘。为了值得担心如何编写它,需要保存多少磁盘空间意味着序列化的开销并不太重要。例如上面的例子,文件大小会大得多,但仍然使用相同的磁盘空间。 - Peter Lawrey
@owlstead 我同意一些 int 或 long 类型的操作是有用的。 - Peter Lawrey
BitSet的序列化肯定是一种可能性,因为它在内部只是存储长整型值,所以开销量会是一个(或多或少)常数,而不是位大小的倍数。API的序列化部分中有这样的说明。当然,这将使其依赖于语言和API(版本),这可能不是您想要的。 - Maarten Bodewes
显示剩余3条评论

6
新创建的,专为您提供。我将把BooleanInputStream留给你作为练习。请注意,文件中的第一个位现在是右侧(MSB)位(在示例中删除Byte.SIZE - 1 -以获取其他字节顺序,无论您喜欢哪种方式)。只需使用例如DataOutputStream首先将东西的大小写入文件。10K可以适合一个整数。
请注意,存储10K元素的布尔数组在内存方面非常低效,您肯定应该使用BitSet来完成这项工作(最终,需要BitSet的人!)。
public final class BooleanOutputStream extends FilterOutputStream {

    private int bitIndex;
    private byte buffer;

    public BooleanOutputStream(final OutputStream out) {
        super(out);
    }

    public void writeBoolean(final boolean value) throws IOException {
        buffer ^= (value ? 1 : 0) << (Byte.SIZE - 1 - bitIndex++);
        if (bitIndex == Byte.SIZE) {
            write(buffer & 0xFF);
            buffer = 0;
            bitIndex = 0;
        }
    }

    /**
     * This is an encoder and does therefore not close the underlying stream.
     * Please close underlying stream separately.
     */
    public void close() throws IOException {
        if (bitIndex != 0) {
            out.write(buffer);
            buffer = 0;
            bitIndex = 0;
        }
    }
}

public class BooleanInputStream extends FilterInputStream {

    private int bitIndex;
    private byte buffer;

    public BooleanInputStream(final InputStream in) {
        super(in);
    }

    public boolean readBoolean() throws IOException {
        if (bitIndex == 0) {
            int b = read();
            if (b == -1) {
                throw new EOFException();
            }
            buffer = (byte) b;
        }

        boolean value = (buffer & (1 << (Byte.SIZE - 1 - bitIndex++))) != 0;
        if (bitIndex == Byte.SIZE) {
            bitIndex = 0;
        }
        return value;
    }

    /**
     * This is a decoder and therefore does not close the underlying stream.
     * Please close underlying stream separately.
     */    
    public void close() throws IOException {
        buffer = 0;
        bitIndex = 0;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接