簡體   English   中英

是否可以使用 JAVA 從文件中讀取/寫入位?

[英]Is it possible to read/write bits from a file using JAVA?

為了讀/寫二進制文件,我使用DataInputStream/DataOutputStream,他們有這個方法writeByte()/readByte(),但我想做的是讀/寫位? 是否可以?

我想將它用於壓縮算法,所以當我壓縮時,我想寫 3 位(對於一個數字,文件中有數百萬個這樣的數字),如果我每次都寫一個字節,我需要寫 3 位,我會寫大量的冗余數據......

無法直接讀取/寫入單個位,您可以讀取/寫入的最小單位是一個字節。

您可以使用標准的按位運算符來操作一個字節,例如,要獲得一個字節的最低 2 位,您可以這樣做

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

將低 4 位設置為 1,並寫入字節:

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

(注意,為了效率起見,您可能想要讀/寫字節數組而不是單個字節)

沒有辦法直接做到這一點。 計算機可以處理的最小單位是一個字節(即使是布爾值也占用一個字節)。 但是,您可以創建一個自定義流類,用您想要的位打包一個字節然后寫入它。 然后你可以為這個類制作一個包裝器,它的 write 函數采用一些整數類型,檢查它是否在 0 和 7(或 -4 和 3 ......或其他)之間,以與 BitInputStream 類相同的方式提取位(下面)會,並對 BitOutputStream 的 write 方法進行相應的調用。 您可能會認為您可以只制作一組 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 從而避免必須反轉輸入,但我不認為很難。

此外,您可能會注意到,有檢測到最后一位在此實現讀取本地沒有辦法,但我真的不想覺得辛苦。

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.");
    }

}

請查看我的 bit-io 庫https://github.com/jinahya/bit-io ,它可以讀取和寫入非八位字節對齊的值,例如 1 位布爾值或 17 位無符號整數。

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

該庫讀取和寫入任意長度的位。

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

InputStreams 和 OutputStreams 是字節流。

要讀取位,您需要讀取一個字節,然后使用位操作來檢查您關心的位。 同樣,要寫入位,您需要寫入包含所需位的字節。

是和否。 在大多數現代計算機上,字節是內存的最小可尋址單位,因此一次只能讀/寫整個字節。 但是,您始終可以使用按位運算符來操作字節中的位。

如果您只是將位寫入文件,Java 的BitSet 類可能值得一看。 從javadoc:

這個類實現了一個根據需要增長的位向量。 位集的每個組件都有一個布爾值。 BitSet 的位由非負整數索引。 可以檢查、設置或清除單個索引位。 一個 BitSet 可用於通過邏輯 AND、邏輯包含 OR 和邏輯異或運算來修改另一個 BitSet 的內容。

您可以將 BitSet 轉換為 long[] 和 byte[] 以將數據保存到文件中。

位以字節為單位打包,除了 VHDL/Verilog 之外,我還沒有看到任何語言可以讓您將單個位附加到流中。 緩存您的位並將它們打包成一個字節以使用緩沖區和位掩碼進行寫入。 對讀取執行相反的操作,即在緩沖區中保留一個指針,並在返回單獨的屏蔽位時遞增它。

Afaik 在 Java API 中沒有執行此操作的功能。 但是,您當然可以讀取一個字節,然后使用位操作函數。 寫作也是一樣。

下面的代碼應該工作

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM