Подтвердить что ты не робот

Как кэшировать InputStream для многократного использования

У меня есть InputStream файла, и я использую компоненты apache poi для чтения из него следующим образом:

POIFSFileSystem fileSystem = new POIFSFileSystem(inputStream);

Проблема в том, что мне нужно использовать один и тот же поток несколько раз, и POIFSFileSystem закрывает поток после использования.

Каков наилучший способ кэширования данных из входного потока, а затем использовать больше входных потоков для разных POIFSFileSystem?

ИЗМЕНИТЬ 1:

Кэш я имел в виду хранилище для последующего использования, а не как способ ускорить приложение. Также лучше просто прочитать входной поток в массив или строку, а затем создать входные потоки для каждого использования?

ИЗМЕНИТЬ 2:

Извините, что вы повторно открываете вопрос, но при работе внутри рабочего стола и веб-приложения условия несколько отличаются. Прежде всего, InputStream я получаю из org.apache.commons.fileupload.FileItem в моем веб-приложении tomcat не поддерживает маркировки, поэтому не может reset.

Во-вторых, я хотел бы сохранить файл в памяти для более быстрого доступа и меньших проблем при работе с файлами.

4b9b3361

Ответ 1

вы можете украсить InputStream, который передается POIFSFileSystem, с версией, которая при вызове функции close() отвечает reset():

class ResetOnCloseInputStream extends InputStream {

    private final InputStream decorated;

    public ResetOnCloseInputStream(InputStream anInputStream) {
        if (!anInputStream.markSupported()) {
            throw new IllegalArgumentException("marking not supported");
        }

        anInputStream.mark( 1 << 24); // magic constant: BEWARE
        decorated = anInputStream;
    }

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

    @Override
    public int read() throws IOException {
        return decorated.read();
    }
}

TestCase

static void closeAfterInputStreamIsConsumed(InputStream is)
        throws IOException {
    int r;

    while ((r = is.read()) != -1) {
        System.out.println(r);
    }

    is.close();
    System.out.println("=========");

}

public static void main(String[] args) throws IOException {
    InputStream is = new ByteArrayInputStream("sample".getBytes());
    ResetOnCloseInputStream decoratedIs = new ResetOnCloseInputStream(is);
    closeAfterInputStreamIsConsumed(decoratedIs);
    closeAfterInputStreamIsConsumed(decoratedIs);
    closeAfterInputStreamIsConsumed(is);
}

EDIT 2

вы можете прочитать весь файл в байте [] (режим slurp), а затем передать его в ByteArrayInputStream

Ответ 2

Попробуйте BufferedInputStream, который добавляет функцию mark и reset в другой поток ввода и просто переопределяет метод закрытия:

public class UnclosableBufferedInputStream extends BufferedInputStream {

    public UnclosableBufferedInputStream(InputStream in) {
        super(in);
        super.mark(Integer.MAX_VALUE);
    }

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

Итак:

UnclosableBufferedInputStream  bis = new UnclosableBufferedInputStream (inputStream);

и используйте bis везде, где ранее использовался метод inputStream.

Ответ 3

Это работает правильно:

byte[] bytes = getBytes(inputStream);
POIFSFileSystem fileSystem = new POIFSFileSystem(new ByteArrayInputStream(bytes));

где getBytes выглядит следующим образом:

private static byte[] getBytes(InputStream is) throws IOException {
    byte[] buffer = new byte[8192];
ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
int n;
baos.reset();

while ((n = is.read(buffer, 0, buffer.length)) != -1) {
      baos.write(buffer, 0, n);
    }

   return baos.toByteArray();
 }

Ответ 4

Использовать ниже для более удобного использования -

public class ReusableBufferedInputStream extends BufferedInputStream
{

    private int totalUse;
    private int used;

    public ReusableBufferedInputStream(InputStream in, Integer totalUse)
    {
        super(in);
        if (totalUse > 1)
        {
            super.mark(Integer.MAX_VALUE);
            this.totalUse = totalUse;
            this.used = 1;
        }
        else
        {
            this.totalUse = 1;
            this.used = 1;
        }
    }

    @Override
    public void close() throws IOException
    {
        if (used < totalUse)
        {
            super.reset();
            ++used;
        }
        else
        {
            super.close();
        }
    }
}

Ответ 5

Если файл не такой большой, прочитайте его в массив byte[] и укажите POI a ByteArrayInputStream, созданный из этого массива.

Если файл большой, тогда вам все равно, поскольку ОС будет делать кэширование для вас как можно лучше.

[EDIT] Использовать Apache commons-io для эффективного чтения файла в массив байтов. Не используйте int read(), так как он читает байтовый файл байтом, который очень медленный!

Если вы хотите сделать это самостоятельно, используйте объект File для получения длины, создайте массив и цикл, который считывает байты из файла. Вы должны зацикливаться, поскольку read(byte[], int offset, int len) может читать меньше len байтов (и обычно делает это).

Ответ 6

Что именно вы имеете в виду с "кешем"? Вы хотите, чтобы разные POIFSFileSystem начинались в начале потока? Если это так, в вашем коде Java абсолютно нет точки кеширования; это будет сделано ОС, просто откройте новый поток.

Или вы хотите продолжить чтение в том месте, где остановилась первая POIFSFileSystem? Это не кеширование, и это очень сложно сделать. Единственный способ, которым я могу думать, если вы не можете избежать закрытия потока, - написать тонкую обертку, которая подсчитывает, сколько байтов было прочитано, а затем открыть новый поток и пропустить это количество байтов. Но это может завершиться неудачно, когда POIFSFileSystem внутренне использует что-то вроде BufferedInputStream.

Ответ 7

Вот как я мог бы быть реализован для безопасного использования с любым InputStream:

  • напишите собственную оболочку InputStream, где вы создаете временный файл, чтобы зеркалировать исходный поток.
  • сбрасывать все данные из исходного потока ввода в этот временный файл
  • Когда поток был полностью прочитан, вы будете иметь все данные, отраженные во временном файле
  • используйте InputStream.reset для переключения (инициализации) внутреннего потока на FileInputStream (mirrored_content_file)
  • отныне вы потеряете ссылку исходного потока (можно собрать)
  • добавьте новый метод release(), который удалит временный файл и освободит любой открытый поток.
  • вы можете даже вызывать release() из finalize, чтобы быть уверенным, что временный файл будет выпущен, если вы забудете вызвать release() (большую часть времени вы должны избегать использования finalize, всегда вызывайте метод для выделения ресурсов объекта). см. Зачем вам когда-либо реализовывать finalize()?

Ответ 8

public static void main(String[] args) throws IOException {
    BufferedInputStream inputStream = new BufferedInputStream(IOUtils.toInputStream("Foobar"));
    inputStream.mark(Integer.MAX_VALUE);
    System.out.println(IOUtils.toString(inputStream));
    inputStream.reset();
    System.out.println(IOUtils.toString(inputStream));
}

Это работает. IOUtils является частью общих прав ввода-вывода.

Ответ 9

Этот ответ повторяет предыдущие 1 | 2 на BufferInputStream. Основные изменения заключаются в том, что он позволяет бесконечное повторное использование. И заботится о том, чтобы закрыть исходный исходный поток для освобождения системных ресурсов. Ваша ОС определяет ограничение на них, и вы не хотите, чтобы в программе не хватало дескрипторов файлов (это также почему вы должны всегда "потреблять" ответы, например, с apache EntityUtils.consumeQuietly()). EDIT Обновлен код для пользователей gready, которые используют read(buffer, offset, length), в этом случае может случиться так, что BufferedInputStream пытается найти источник, этот код защищает от использования.

public class CachingInputStream extends BufferedInputStream {    
    public CachingInputStream(InputStream source) {
        super(new PostCloseProtection(source));
        super.mark(Integer.MAX_VALUE);
    }

    @Override
    public synchronized void close() throws IOException {
        if (!((PostCloseProtection) in).decoratedClosed) {
            in.close();
        }
        super.reset();
    }

    private static class PostCloseProtection extends InputStream {
        private volatile boolean decoratedClosed = false;
        private final InputStream source;

        public PostCloseProtection(InputStream source) {
            this.source = source;
        }

        @Override
        public int read() throws IOException {
            return decoratedClosed ? -1 : source.read();
        }

        @Override
        public int read(byte[] b) throws IOException {
            return decoratedClosed ? -1 : source.read(b);
        }

        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            return decoratedClosed ? -1 : source.read(b, off, len);
        }

        @Override
        public long skip(long n) throws IOException {
            return decoratedClosed ? 0 : source.skip(n);
        }

        @Override
        public int available() throws IOException {
            return source.available();
        }

        @Override
        public void close() throws IOException {
            decoratedClosed = true;
            source.close();
        }

        @Override
        public void mark(int readLimit) {
            source.mark(readLimit);
        }

        @Override
        public void reset() throws IOException {
            source.reset();
        }

        @Override
        public boolean markSupported() {
            return source.markSupported();
        }
    }
}

Чтобы повторно использовать его, просто закройте его, если это не так.

Одно ограничение состоит в том, что если поток закрыт до того, как будет прочитано все содержимое исходного потока, то этот декоратор будет иметь неполные данные, поэтому убедитесь, что весь поток прочитан до закрытия.

Ответ 10

Я просто добавляю свое решение здесь, так как это работает для меня. В основном это комбинация двух верхних ответов:)

    private String convertStreamToString(InputStream is) {
    Writer w = new StringWriter();
    char[] buf = new char[1024];
    Reader r;
    is.mark(1 << 24);
    try {
        r = new BufferedReader(new InputStreamReader(is, "UTF-8"));
        int n;
        while ((n=r.read(buf)) != -1) {
            w.write(buf, 0, n);
        }
        is.reset();
    } catch(UnsupportedEncodingException e) {
        Logger.debug(this.getClass(), "Cannot convert stream to string.", e);
    } catch(IOException e) {
        Logger.debug(this.getClass(), "Cannot convert stream to string.", e);
    }
    return w.toString();
}