是否可以使用JAVA从文件读取/写入位?

时间:2010-11-18 23:55:57

标签: java file-io bit-manipulation

要读/写二进制文件,我使用DataInputStream / DataOutputStream,他们有这个方法writeByte()/ readByte(),但我想做的是读/写位?可能吗?

我想将它用于压缩算法,所以当我压缩时我想写3位(一个数字,文件中有数百万这样的数字),如果我每次写一个字节,我需要写3位,我将写入大量冗余数据......

9 个答案:

答案 0 :(得分:6)

直接读/写单​​个位是不可能的,你可以读/写的最小单位是一个字节。

您可以使用标准bitwise运算符来操作一个字节,例如要获得一个字节的最低2位,你可以

byte b = in.readByte();
byte lowBits = b&0x3;

将低4位设置为1,并写入字节:

b |= 0xf;
out.writeByte(b);

(注意,为了提高效率,您可能希望读/写字节数组而不是单字节)

答案 1 :(得分:5)

没有办法直接这样做。计算机可以处理的最小单位是一个字节(甚至布尔占用一个字节)。但是,您可以创建一个自定义流类,该类使用您想要的位打包一个字节然后写入它。然后你可以为这个类创建一个包装器,它的写入函数采用一些整数类型,检查它是否在0到7之间(或者-4和3 ......或者其他),从中提取出来。与BitInputStream类(如下所示)相同,并对BitOutputStream的写入方法进行相应的调用。你可能会认为你可以制作一组IO流类,但3不能均匀地进入8。因此,如果您希望获得最佳的存储效率,并且您不想真正努力工作,那么您就会陷入两层抽象的困境。下面是一个BitOutputStream类,一个相应的BitInputStream类,以及一个确保它们工作的程序。

import java.io.IOException;
import java.io.OutputStream;

class BitOutputStream {

    private OutputStream out;
    private boolean[] buffer = new boolean[8];
    private int count = 0;

    public BitOutputStream(OutputStream out) {
        this.out = out;
    }

    public void write(boolean x) throws IOException {
        this.count++;
        this.buffer[8-this.count] = x;
        if (this.count == 8){
            int num = 0;
            for (int index = 0; index < 8; index++){
                num = 2*num + (this.buffer[index] ? 1 : 0);
            }

            this.out.write(num - 128);

            this.count = 0;
        }
    }

    public void close() throws IOException {
        int num = 0;
        for (int index = 0; index < 8; index++){
            num = 2*num + (this.buffer[index] ? 1 : 0);
        }

        this.out.write(num - 128);

        this.out.close();
    }

}

我确定有一种方法可以使用逐位运算符来填充int,从而避免不得不反转输入,但我不会那么认为那么难。

另外,您可能已经注意到在此实现中没有本地方法可以检测到最后一位已被读取,但我确实并不想那个< / em>很难。

import java.io.IOException;
import java.io.InputStream;

class BitInputStream {

    private InputStream in;
    private int num = 0;
    private int count = 8;

    public BitInputStream(InputStream in) {
        this.in = in;
    }

    public boolean read() throws IOException {
        if (this.count == 8){
            this.num = this.in.read() + 128;
            this.count = 0;
        }

        boolean x = (num%2 == 1);
        num /= 2;
        this.count++;

        return x;
    }

    public void close() throws IOException {
        this.in.close();
    }

}

您可能知道这一点,但是您应该在BitStream和FileStream之间放置一个BufferedStream,否则它将永远消失。

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Random;

class Test {

    private static final int n = 1000000;

    public static void main(String[] args) throws IOException {

        Random random = new Random();

        //Generate array

        long startTime = System.nanoTime();

        boolean[] outputArray = new boolean[n];
        for (int index = 0; index < n; index++){
            outputArray[index] = random.nextBoolean();
        }

        System.out.println("Array generated in " + (double)(System.nanoTime() - startTime)/1000/1000/1000 + " seconds.");

        //Write to file

        startTime = System.nanoTime();

        BitOutputStream fout = new BitOutputStream(new BufferedOutputStream(new FileOutputStream("booleans.bin")));

        for (int index = 0; index < n; index++){
            fout.write(outputArray[index]);
        }

        fout.close();

        System.out.println("Array written to file in " + (double)(System.nanoTime() - startTime)/1000/1000/1000 + " seconds.");

        //Read from file

        startTime = System.nanoTime();

        BitInputStream fin = new BitInputStream(new BufferedInputStream(new FileInputStream("booleans.bin")));

        boolean[] inputArray = new boolean[n];
        for (int index = 0; index < n; index++){
            inputArray[index] = fin.read();
        }

        fin.close();

        System.out.println("Array read from file in " + (double)(System.nanoTime() - startTime)/1000/1000/1000 + " seconds.");

        //Delete file
        new File("booleans.bin").delete();

        //Check equality

        boolean equal = true;
        for (int index = 0; index < n; index++){
            if (outputArray[index] != inputArray[index]){
                equal = false;
                break;
            }
        }

        System.out.println("Input " + (equal ? "equals " : "doesn't equal ") + "output.");
    }

}

答案 2 :(得分:2)

InputStreams和OutputStreams是字节流。

要读取一点,您需要读取一个字节,然后使用位操作来检查您关心的位。同样,要写入位,您需要写入包含所需位的字节。

答案 3 :(得分:2)

是和否。在大多数现代计算机上,字节是内存的最小可寻址单位,因此您一次只能读/写整个字节。但是,您始终可以使用按位运算符来操作一个字节内的位。

答案 4 :(得分:1)

比特以字节打包,除了VHDL / Verilog之外,我没有看到任何允许您将单个位附加到流的语言。缓存您的位并将它们打包成一个字节,以便使用缓冲区和bitmasking进行写入。执行相反的操作,即在缓冲区中保留一个指针,并在单独返回屏蔽位时递增指针。

答案 5 :(得分:1)

Afaik在Java API中没有执行此操作的功能。但是,您当然可以读取一个字节,然后使用位操作函数。写作也是如此。

答案 6 :(得分:1)

已移至https://github.com/jinahya/bit-io

请查看http://jinahya.googlecode.com/svn/trunk/com.googlecode.jinahya/bit-io/src/main/java/com/googlecode/jinahya/io/

<dependency>
  <!-- resides in central repo -->
  <groupId>com.googlecode.jinahya</groupId>
  <artifactId>bit-io</artifactId>
  <version>1.0-alpha-13</version>
</dependency>

这是一个小型的便捷库,用于使用Java读取/写入任意长度的位。

final InputStream stream;
final BitInput input = new BitInput(new BitInput.StreamInput(stream));

final int b = input.readBoolean(); // reads a 1-bit boolean value
final int i = input.readUnsignedInt(3); // reads a 3-bit unsigned int
final long l = input.readLong(47); // reads a 47-bit signed long

input.align(1); // 8-bit byte align; padding


final WritableByteChannel channel;
final BitOutput output = new BitOutput(new BitOutput.ChannelOutput(channel));

output.writeBoolean(true); // writes a 1-bit boolean value
output.writeInt(17, 0x00); // writes a 17-bit signed int
output.writeUnsignedLong(54, 0x00L); // writes a 54-bit unsigned long

output.align(4); // 32-bit byte align; discarding

答案 7 :(得分:1)

如果您只是将位写入文件,那么Java的BitSet class可能值得一看。来自javadoc:

  

该类实现了一个根据需要增长的位向量。位组的每个组件都有一个布尔值。 BitSet的位由非负整数索引。可以检查,设置或清除各个索引位。一个BitSet可用于通过逻辑AND,逻辑包含OR和逻辑异或运算来修改另一个BitSet的内容。

您可以将BitSet转换为long []和byte []以将数据保存到文件中。

答案 8 :(得分:0)

以下代码应该有效

    int[] mynumbers = {3,4};
    BitSet compressedNumbers = new BitSet(mynumbers.length*3);
    // let's say you encoded 3 as 101 and 4 as 010
    String myNumbersAsBinaryString = "101010"; 
    for (int i = 0; i < myNumbersAsBinaryString.length(); i++) {
        if(myNumbersAsBinaryString.charAt(i) == '1')
            compressedNumbers.set(i);
    }
    String path = Resources.getResource("myfile.out").getPath();
    ObjectOutputStream outputStream = null;
    try {
        outputStream = new ObjectOutputStream(new FileOutputStream(path));
        outputStream.writeObject(compressedNumbers);
    } catch (IOException e) {
        e.printStackTrace();
    }