簡體   English   中英

在 Java 中將文件的前 N ​​個字節作為 InputStream 讀取?

[英]Reading first N bytes of a file as an InputStream in Java?

對於我的一生,我一直無法找到與我想要做的事情相匹配的問題,所以我將在這里解釋我的用例。 如果您知道某個主題已經涵蓋了該問題的答案,請隨時將我引向該主題。 :)

我有一段代碼可以定期(每 20 秒)將文件上傳到 Amazon S3。 該文件是由另一個進程寫入的日志文件,因此此功能實際上是一種拖尾日志的手段,以便有人可以半實時地讀取其內容,而無需直接訪問日志所在的機器.

直到最近,我一直只是使用 S3 PutObject 方法(使用文件作為輸入)來執行此上傳。 但在 AWS SDK 1.9 中,這不再有效,因為如果實際上傳的內容大小大於上傳開始時承諾的內容長度,S3 客戶端將拒絕請求。 此方法在開始流式傳輸數據之前讀取文件的大小,因此鑒於此應用程序的性質,文件很可能在該點和流末尾之間增加了大小。 這意味着我現在需要確保我只發送 N 個字節的數據,而不管文件有多大。

我不需要以任何方式解釋文件中的字節,所以我不關心編碼。 我可以逐字節傳輸它。 基本上,我想要的是一種簡單的方法,我可以將文件讀取到第 N 個字節,然后即使文件中有更多數據超過該點,也可以終止讀取。 (換句話說,在特定點將 EOF 插入到流中。)

例如,如果我開始上傳時我的文件長 10000 字節,但在上傳過程中增長到 12000 字節,我想在 10000 字節時停止上傳,無論大小如何變化。 (在隨后的上傳中,我將上傳 12000 字節或更多。)

我還沒有找到一種預制的方法來做到這一點 - 到目前為止我發現的最好的似乎是 IOUtils.copyLarge(InputStream, OutputStream, offset, length),它可以被告知復制最大的“長度”字節到提供的 OutputStream。 但是,copyLarge 是一種阻塞方法,PutObject(它大概在其 InputStream 上調用了一種 read() 形式)也是一種阻塞方法,所以似乎我根本無法讓它工作。

我還沒有找到任何可以做到這一點的方法或預構建的流,所以這讓我覺得我需要編寫自己的實現來直接監控已讀取的字節數。 這可能會像 BufferedInputStream 一樣工作,其中每批讀取的字節數是緩沖區大小或要讀取的剩余字節中的較小者。 (例如,緩沖區大小為 3000 字節,我會做三批,每批 3000 字節,然后是一批 1000 字節 + EOF。)

有誰知道更好的方法來做到這一點? 謝謝。

編輯只是為了澄清,我已經知道了幾個替代方案,它們都不理想:

(1) 我可以在上傳文件時鎖定文件。 這樣做會在寫入文件的過程中導致數據丟失或操作問題。

(2) 我可以在上傳文件之前創建文件的本地副本。 這可能非常低效並占用大量不必要的磁盤空間(此文件可能會增長到幾 GB 的范圍,並且運行它的機器可能缺少磁盤空間)。

編輯 2:根據同事的建議,我的最終解決方案如下所示:

private void uploadLogFile(final File logFile) {
    if (logFile.exists()) {
        long byteLength = logFile.length();
        try (
            FileInputStream fileStream = new FileInputStream(logFile);
            InputStream limitStream = ByteStreams.limit(fileStream, byteLength);
        ) {
            ObjectMetadata md = new ObjectMetadata();
            md.setContentLength(byteLength);
            // Set other metadata as appropriate.
            PutObjectRequest req = new PutObjectRequest(bucket, key, limitStream, md);
            s3Client.putObject(req);
        } // plus exception handling
    }
}

LimitInputStream 是我同事建議的,顯然不知道它已被棄用。 ByteStreams.limit 是當前的番石榴替代品,它可以滿足我的需求。 謝謝大家。

完整的答案翻錄和替換:

包裝InputStream相對簡單,例如在發送數據結束之前限制它將傳遞的字節數。 FilterInputStream的目標是這種一般性的工作,但是由於您必須覆蓋該特定工作的幾乎所有方法,因此它只會妨礙您。

這是一個粗略的解決方案:

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

/**
 * An {@code InputStream} wrapper that provides up to a maximum number of
 * bytes from the underlying stream.  Does not support mark/reset, even
 * when the wrapped stream does, and does not perform any buffering.
 */
public class BoundedInputStream extends InputStream {

    /** This stream's underlying @{code InputStream} */
    private final InputStream data;

    /** The maximum number of bytes still available from this stream */ 
    private long bytesRemaining;

    /**
     * Initializes a new {@code BoundedInputStream} with the specified
     * underlying stream and byte limit
     * @param data the @{code InputStream} serving as the source of this
     *        one's data
     * @param maxBytes the maximum number of bytes this stream will deliver
     *        before signaling end-of-data
     */
    public BoundedInputStream(InputStream data, long maxBytes) {
        this.data = data;
        bytesRemaining = Math.max(maxBytes, 0);
    }

    @Override
    public int available() throws IOException {
        return (int) Math.min(data.available(), bytesRemaining);
    }

    @Override
    public void close() throws IOException {
        data.close();
    }

    @Override
    public synchronized void mark(int limit) {
        // does nothing
    }

    @Override
    public boolean markSupported() {
        return false;
    }

    @Override
    public int read(byte[] buf, int off, int len) throws IOException {
        if (bytesRemaining > 0) {
            int nRead = data.read(
                    buf, off, (int) Math.min(len, bytesRemaining));

            bytesRemaining -= nRead;

            return nRead;
        } else {
            return -1;
        }
    }

    @Override
    public int read(byte[] buf) throws IOException {
        return this.read(buf, 0, buf.length);
    }

    @Override
    public synchronized void reset() throws IOException {
        throw new IOException("reset() not supported");
    }

    @Override
    public long skip(long n) throws IOException {
        long skipped = data.skip(Math.min(n, bytesRemaining));

        bytesRemaining -= skipped;

        return skipped;
    }

    @Override
    public int read() throws IOException {
        if (bytesRemaining > 0) {
            int c = data.read();

            if (c >= 0) {
                bytesRemaining -= 1;
            }

            return c;
        } else {
            return -1;
        }
    }
}

暫無
暫無

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

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