Чтение первых N байтов файла как InputStream в Java?

1

Для моей жизни я не смог найти вопрос, который соответствует тому, что я пытаюсь сделать, поэтому я объясню, что я делаю здесь. Если вы знаете тему, которая уже охватывает ответ на этот вопрос, пожалуйста, не стесняйтесь направить меня к этому. :)

У меня есть кусок кода, который периодически загружает файл в Amazon S3 (каждые 20 секунд). Файл представляет собой файл журнала, написанный другим процессом, поэтому эта функция является эффективным средством для ограничения журнала, чтобы кто-то мог прочитать его содержимое в полурежиме, не имея прямого доступа к машине, на которой находится журнал,

До недавнего времени я просто использовал метод S3 PutObject (используя входной файл) для этой загрузки. Но в AWS SDK 1.9 это больше не работает, потому что клиент S3 отклоняет запрос, если фактический размер содержимого больше, чем длина контента, которая была обещана в начале загрузки. Этот метод считывает размер файла до того, как он начнет передавать данные, поэтому, учитывая природу этого приложения, файл, вероятно, увеличится в размере между этой точкой и концом потока. Это означает, что мне нужно теперь гарантировать, что я отправляю только N байтов данных независимо от того, насколько большой файл.

Мне не нужно каким-либо образом интерпретировать байты в файле, поэтому меня не интересует кодировка. Я могу передать его байтом за байт. В принципе, я хочу простой метод, где я могу прочитать файл до N-го байта, а затем завершить его, даже если в этом файле больше данных. (Другими словами, вставьте EOF в поток в определенной точке.)

Например, если мой файл имеет длину 10000 байт, когда я запускаю загрузку, но при загрузке увеличивается до 12000 байт, я хочу прекратить загрузку в 10000 байт независимо от этого изменения размера. (При последующей загрузке я бы затем загрузил 12000 байт или более.)

Я не нашел готового способа сделать это - лучшее, что я нашел до сих пор, похоже, это IOUtils.copyLarge(InputStream, OutputStream, offset, length), которому можно было бы скопировать максимум "длины", байтов к предоставленному OutputStream. Однако copyLarge является методом блокировки, как и PutObject (который предположительно вызывает форму read() для InputStream), поэтому кажется, что я не мог заставить это работать вообще.

Я не нашел каких-либо методов или готовых потоков, которые могут это сделать, поэтому он заставляет меня думать, что мне нужно будет написать собственную реализацию, которая напрямую контролирует, сколько байтов было прочитано. Вероятно, это будет работать, как BufferedInputStream, где количество прочитанных байтов за партию меньше размера буфера или оставшихся байтов для чтения. (например, с размером буфера 3000 байт, я бы сделал три партии по 3000 байт каждый, а затем пакет с 1000 байтами + EOF.)

Кто-нибудь знает лучший способ сделать это? Благодарю.

EDIT Просто для уточнения, я уже знаю пару альтернатив, ни один из которых не идеален:

(1) Я могу заблокировать файл при его загрузке. Это приведет к потере данных или рабочих проблем в процессе записи файла.

(2) Я могу создать локальную копию файла перед его загрузкой. Это может быть очень неэффективным и занимать много ненужного дискового пространства (этот файл может вырасти до диапазона в несколько гигабайт, а работающий на нем аппарат может быть меньше места на диске).

EDIT 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 - текущая замена Guava, и она делает то, что я хочу. Всем спасибо.

  • 0
    Почему сложно выполнить блокировку ввода-вывода? Особенно если учесть, что ты этим занимался раньше?
  • 0
    Вы можете с помощью всего лишь нескольких строк кода расширить FilterInputStream чтобы он притворился, что существует условие EOF после чтения не более N байтов.
Показать ещё 4 комментария
Теги:
inputstream
filestream

1 ответ

4
Лучший ответ

Полный ответ rip & replace:

Сравнительно просто обернуть 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 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 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;
        }
    }
}
  • 0
    Таким образом, это будет нормально работать (и IOUtils обеспечивает почти такую же реализацию), за исключением того, что метод S3 PutObject не дает мне никакого контроля над тем, как читается поток. Предположительно, он просто вызывает InputStream.read (), пока поток не вернет EOF. Поэтому мне нужен способ контроля отправляемых данных. Но часть вашего кода там похожа на то, что я уже изучал - в основном я просто хочу быть уверен, что я не изобретаю колесо заново.
  • 0
    Так что я, видимо, неправильно тебя понял. Я думал, что вы искали альтернативу PutObject , имея какой-то другой способ получить соответствующий OutputStream для направления байтов. Для того, чтобы что-то подключить к PutObject , лучше всего подойдет подход, предложенный @ 5gon12eder. Возможно, я могу предложить некоторые детали.
Показать ещё 4 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню