J'ai un InputStream d'un fichier et j'utilise des composants Apache poi pour le lire comme ceci:
POIFSFileSystem fileSystem = new POIFSFileSystem(inputStream);
Le problème est que je dois utiliser le même flux plusieurs fois et le POIFSFileSystem ferme le flux après utilisation.
Quel est le meilleur moyen de mettre en cache les données du flux d'entrée, puis de servir davantage de flux d'entrée à différents POIFSFileSystem?
EDIT 1:
Par cache, je voulais dire stocker pour une utilisation ultérieure, pas comme un moyen d'accélérer l'application. Est-il également préférable de simplement lire le flux d'entrée dans un tableau ou une chaîne, puis de créer des flux d'entrée pour chaque utilisation?
EDIT 2:
Désolé de rouvrir la question, mais les conditions sont quelque peu différentes lorsque vous travaillez dans une application de bureau ou Web. Tout d’abord, InputStream i provient de org.Apache.commons.fileupload.FileItem dans mon application Web Tomcat ne prend pas en charge les marquages et ne peut donc pas être réinitialisé.
Deuxièmement, j'aimerais pouvoir garder le fichier en mémoire pour un accès plus rapide et moins de problèmes lors de l'utilisation de fichiers.
vous pouvez décorer InputStream transmis à POIFSFileSystem avec une version qui, lorsqu'elle est appelée, s'appelle, répond avec 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);
}
vous pouvez lire le fichier entier dans un octet [] (mode Slurp) puis le transmettre à un ByteArrayInputStream
Essayez BufferedInputStream, qui ajoute des fonctionnalités de marquage et de réinitialisation à un autre flux d'entrée et substituez simplement sa méthode de fermeture:
public class UnclosableBufferedInputStream extends BufferedInputStream {
public UnclosableBufferedInputStream(InputStream in) {
super(in);
super.mark(Integer.MAX_VALUE);
}
@Override
public void close() throws IOException {
super.reset();
}
}
Alors:
UnclosableBufferedInputStream bis = new UnclosableBufferedInputStream (inputStream);
et utilisez bis
partout où inputStream était utilisé auparavant.
Cela fonctionne correctement:
byte[] bytes = getBytes(inputStream);
POIFSFileSystem fileSystem = new POIFSFileSystem(new ByteArrayInputStream(bytes));
où getBytes est comme ceci:
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();
}
Utilisez ci-dessous l'implémentation pour une utilisation plus personnalisée -
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();
}
}
}
Si le fichier n'est pas si gros, lisez-le dans un tableau byte[]
et donnez à POI une ByteArrayInputStream
créée à partir de ce tableau.
Si le fichier est volumineux, vous ne devriez pas vous en soucier, car le système d'exploitation fera la mise en cache pour vous le mieux possible.
[EDIT] Utilisez Apache commons-io pour lire le fichier dans un tableau d'octets de manière efficace. N'utilisez pas int read()
car il lit le fichier octet par octet, ce qui correspond à very slow!
Si vous voulez le faire vous-même, utilisez un objet File
pour obtenir la longueur, créez le tableau et la boucle qui lit les octets du fichier. Vous devez effectuer une boucle puisque read(byte[], int offset, int len)
peut lire moins de len
octets (et le fait généralement).
Que voulez-vous dire exactement par "cache"? Voulez-vous que les différents POIFSFileSystem commencent au début du flux? Si tel est le cas, il est absolument inutile de mettre en cache quoi que ce soit dans votre code Java. cela sera fait par le système d'exploitation, ouvrez simplement un nouveau flux.
Ou voulez-vous continuer à lire au point où le premier POIFSFileSystem s'est arrêté? Ce n'est pas la mise en cache, et c'est très difficile à faire. Le seul moyen auquel je peux penser si vous ne pouvez pas éviter la fermeture du flux consiste à écrire un wrapper fin qui compte le nombre d'octets lus, puis à ouvrir un nouveau flux et à sauter autant d'octets. Mais cela pourrait échouer si POIFSFileSystem utilise en interne quelque chose comme un BufferedInputStream.
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));
}
Cela marche. IOUtils fait partie de commons IO.
Voici comment j'ai implémenté, pour être utilisé en toute sécurité avec n'importe quel InputStream:
Cette réponse itère sur les précédentes 1 | 2 basé sur la BufferInputStream
. Les principaux changements sont que cela permet une réutilisation infinie. Et s'occupe de fermer le flux d'entrée source d'origine pour libérer des ressources système. Votre système d'exploitation définit une limite sur ceux-ci et vous ne voulez pas que le programme manque de descripteurs de fichiers (C'est aussi pourquoi vous devriez toujours «consommer» des réponses, par exemple avec Apache EntityUtils.consumeQuietly()
).EDITMise à jour du code à manipuler pour les gros consommateurs qui utilisent read(buffer, offset, length)
. Dans ce cas, il peut arriver que BufferedInputStream
essaie de rechercher le code source, ce code protège contre cette utilisation.
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();
}
}
}
Pour le réutiliser, fermez-le d'abord s'il ne l'était pas.
Une limitation cependant est que si le flux est fermé avant que tout le contenu du flux original ait été lu, ce décorateur aura alors des données incomplètes. Assurez-vous donc que tout le flux est lu avant la fermeture.
Je viens d'ajouter ma solution ici, car cela fonctionne pour moi. C'est fondamentalement une combinaison des deux premières réponses :)
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();
}