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

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

POIFSFileSystem fileSystem = new POIFSFileSystem(inputStream); 

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

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

ИЗМЕНИТЬ 1:

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

EDIT 2:

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

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

вы можете украсить 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(); } } 

прецедент

 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

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

 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.

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

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

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

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

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

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

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

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

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

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

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

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

Этот ответ повторяет предыдущие 1 | 2 на основе BufferInputStream . Основные изменения заключаются в том, что он позволяет бесконечное повторное использование. И заботится о том, чтобы закрыть исходный исходный stream для освобождения системных ресурсов. Ваша ОС определяет предел для них, и вы не хотите, чтобы в программе закончились файловые дескрипторы ( Вот почему вы всегда должны «потреблять» ответы, например, с помощью 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(); } } } 

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

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

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

  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(); } 
  • Кэширование в asp.net-mvc
  • Наблюдение за устаревшей инструкцией по x86 с самомодифицируемым кодом
  • Android: очистить кеш всех приложений?
  • Как удалить кэш других приложений из нашего приложения для Android?
  • ASP.NET MVC как отключить опцию автоматического кэширования?
  • Поиск простого кэша Java в памяти
  • Как очистить кэш HttpWebRequest
  • AngularJS отключает частичное кэширование на dev-машине
  • Как очистить MemoryCache?
  • Есть ли API, чтобы заставить Facebook снова очистить страницу?
  • NodeJS / express: код состояния кэша и 304
  • Давайте будем гением компьютера.