Question

J'ai un InputStream d'un fichier et j'utilise des composants poi apache à 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.

Quelle est la meilleure façon de mettre en cache les données du flux d'entrée et servir ensuite plusieurs flux d'entrée à différents POIFSFileSystem?

EDIT 1:

En cache je voulais dire stocker pour une utilisation ultérieure, et non comme un moyen de speedup l'application. Aussi est-il préférable de lire tout le flux d'entrée dans un tableau ou une chaîne, puis créer des flux d'entrée pour chaque utilisation?

EDIT 2:

Désolé de rouvrir la question, mais les conditions sont un peu différentes lorsque l'on travaille à l'intérieur de bureau et de l'application Web. Tout d'abord, le InputStream que je reçois de l'org.apache.commons.fileupload.FileItem dans mon application web tomcat ne prend pas en charge les marques ne peut donc pas réinitialiser.

Deuxièmement, je voudrais être en mesure de conserver le fichier dans la mémoire pour plus rapidement accès et moins de problèmes io lorsqu'ils traitent des fichiers.

Était-ce utile?

La solution

vous pouvez décorer InputStream être passé à POIFSFileSystem avec une version que lorsque close () est appelé à répondre 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

vous pouvez lire le fichier entier dans un octet [] (mode slurp), il passe ensuite à un ByteArrayInputStream

Autres conseils

Essayez BufferedInputStream, qui ajoute marque et réinitialiser la fonctionnalité à un autre flux d'entrée, et juste remplacer la méthode proche:

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

et utiliser bis où fluxEntrée a été utilisé avant.

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 la mise en œuvre 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();
        }
    }
}

Que voulez-vous dire par « cache »? Voulez-vous les différents POIFSFileSystem pour commencer au début du cours d'eau? Dans ce cas, il n'y a absolument aucun point quoi que ce soit la mise en cache dans votre code Java; il sera fait par le système d'exploitation, il suffit d'ouvrir un nouveau flux.

Ou vous blême pour poursuivre la lecture à l'endroit où le premier arrêté POIFSFileSystem? Ce n'est pas mise en cache, et il est très difficile à faire. La seule façon que je peux penser si vous ne pouvez pas éviter le flux se fermer serait d'écrire un wrapper mince qui compte le nombre d'octets ont été lus et puis ouvrez un nouveau flux et sauter que beaucoup d'octets. Mais cela pourrait échouer lorsque POIFSFileSystem utilise en interne quelque chose comme un BufferedInputStream.

Si le fichier est pas grand, lire dans un tableau de byte[] et donner un POI ByteArrayInputStream créé à partir de ce tableau.

Si le fichier est grand, alors vous ne devriez pas soin, étant donné que le système d'exploitation fera la mise en cache pour vous le mieux possible.

[EDIT] Utilisez commons-io Apache pour lire le fichier dans un tableau d'octets dans un de manière efficace. Ne pas utiliser int read() car il lit l'octet par octet de fichier qui est très lent!

Si vous voulez faire vous-même, utilisez un objet File pour obtenir la longueur, créer le tableau et une boucle qui lit les octets du fichier. Vous devez boucle depuis read(byte[], int offset, int len) pouvez lire moins de len octets (et habituellement).

Voici comment je mis en œuvre, à utiliser en toute sécurité avec un InputStream:

  • écrire votre propre wrapper InputStream où vous créez un fichier temporaire pour refléter le contenu du flux original
  • tout jeter lecture à partir du flux d'entrée d'origine dans ce fichier temporaire
  • lorsque le flux a été complètement lu, vous aurez toutes les données dans le fichier miroir temporaire
  • InputStream.reset utiliser pour commuter (initialiser) le flux interne à un FileInputStream (mirrored_content_file)
  • à partir de maintenant vous perdrez la référence du flux original (peut être collecté)
  • ajouter une nouvelle version de () qui supprime le fichier temporaire et libérer un cours d'eau ouvert.
  • vous pouvez même appeler la libération () de Finaliser pour vérifier que le fichier temporaire est communiqué au cas où vous oubliez de .L'appel () (la plupart du temps, vous devriez éviter d'utiliser Finaliser , toujours faire appel à une méthode pour libérer des ressources d'objet). voir Pourquoi voudriez-vous mettre en œuvre jamais 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));
}

Cela fonctionne. IOUtils fait partie des communes IO.

Cette réponse itère sur les précédents 1 | 2 sur la base BufferInputStream. Les principaux changements sont qu'il permet une réutilisation infinie. Et prend soin de fermer le flux d'entrée de la source d'origine pour les ressources système en place. Votre OS définit une limite sur ceux-ci et vous ne voulez pas que le programme court de descripteurs de fichiers ( C'est aussi pourquoi vous devriez toujours « consommer » des réponses par exemple avec l'apache EntityUtils.consumeQuietly() ). EDIT Mise à jour le code à gérer pour les consommateurs qui utilisent Gready read(buffer, offset, length), dans ce cas, il peut arriver que BufferedInputStream essaie difficile de regarder la 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 réutiliser tout simplement fermer d'abord si ce n'était pas.

Une limite cependant est que si le courant est fermé avant a été lu tout le contenu original du flux, alors ce décorateur aura des données incomplètes, alors assurez-vous de lire est tout le flux avant la fermeture.

Je viens d'ajouter ici ma solution, comme cela fonctionne pour moi. Il est essentiellement 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();
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top