39 votes

Utilisation de ServletOutputStream pour écrire de très gros fichiers dans un servlet Java sans problèmes de mémoire

Je suis en utilisant IBM Websphere Application Server v6 et Java 1.4 et suis en train d'écrire des fichiers CSV à la ServletOutputStream pour un utilisateur à télécharger. Les fichiers sont allant de 50-750 MO à l'heure actuelle.

Les fichiers plus petits ne sont pas à l'origine de trop de problème, mais avec les fichiers les plus grands, il apparaît qu'il est écrit dans le tas qui est de causer un dépassement de mémoire d'erreur et ramener l'ensemble du serveur.

Ces fichiers ne peuvent être servis à des utilisateurs authentifiés via le protocole https qui est pourquoi je suis à leur service par le biais d'un Servlet au lieu de simplement les coller dans Apache.

Le code que j'utilise est (duvet supprimé autour de ce sujet):

    resp.setHeader("Content-length", "" + fileLength);
    resp.setContentType("application/vnd.ms-excel");
    resp.setHeader("Content-Disposition","attachment; filename=\"export.csv\"");

    FileInputStream inputStream = null;

    try
    {
        inputStream = new FileInputStream(path);
        byte[] buffer = new byte[1024];
        int bytesRead = 0;

        do
        {
        	bytesRead = inputStream.read(buffer, offset, buffer.length);
        	resp.getOutputStream().write(buffer, 0, bytesRead);
        }
        while (bytesRead == buffer.length);

        resp.getOutputStream().flush();
    }
    finally
    {
        if(inputStream != null)
	        inputStream.close();
    }

Le FileInputStream ne semble pas être la cause du problème, comme si j'écris à un autre fichier, ou tout simplement supprimer l'écrire complètement l'utilisation de la mémoire ne semble pas être un problème.

Ce que je pense, c'est que le reee.getOutputStream().écrire dans la mémoire jusqu'à ce que les données peuvent être envoyées par le client. De sorte que la totalité du fichier peut être lu et stocké dans le reee.getOutputStream() la cause de mes problèmes de mémoire et de s'écraser!

J'ai essayé de mise en mémoire Tampon de ces flux et également essayé d'utiliser des Canaux de java.nio, aucun ne semble faire peu de différence à mes problèmes de mémoire. J'ai également vidé le outputstream une fois par itération de la boucle, et après la boucle, ce qui n'aidait pas.

46voto

BalusC Points 498232

La moyenne décent servletcontainer se vide le flux par défaut de toutes les ~2 KO. Vous devriez vraiment pas avoir le besoin d'appeler explicitement flush() sur le OutputStream de la HttpServletResponse à des intervalles quand séquentiellement les données en continu à partir d'une seule et même source. Par exemple, dans Tomcat (et Websphere!) c'est configureable comme bufferSize de l'attribut HTTP connecteur.

La moyenne décent servletcontainer viens aussi de flux de données en morceaux si la longueur du contenu est inconnu à l'avance (selon la spécification de l'API Servlet!) et si le client supporte le protocole HTTP 1.1.

Les symptômes du problème au moins indiquer que le servletcontainer est la mémoire tampon de l'ensemble du flux de déchets dans la mémoire avant de rincer. Cela peut signifier que le contenu de la longueur de l'en-tête n'est pas définie et/ou la servletcontainer ne prend pas en charge le codage mémorisé en bloc et/ou du côté client ne prend pas en charge le codage mémorisé en bloc (c'est à dire qu'il utilise le protocole HTTP 1.0).

Pour corriger l'une ou l'autre, il suffit de régler la longueur du contenu à l'avance:

response.setHeader("Content-Length", String.valueOf(new File(path).length()));

1voto

flush fonctionne-t-il sur le flux de sortie.

Vraiment, je voulais dire que vous devez utiliser la forme d'écriture à trois arguments car le tampon n'est pas nécessairement entièrement lu (en particulier à la fin du fichier (!)). Un essai / enfin serait également nécessaire, sauf si vous voulez que votre serveur meure de manière inattendue.

1voto

Kevin Hakanson Points 15498

J'ai utilisé une classe qui enveloppe le flux de sortie pour le rendre réutilisable dans d'autres contextes. Cela a bien fonctionné pour moi pour obtenir des données dans le navigateur plus rapidement, mais je n'ai pas examiné les implications de la mémoire. (veuillez pardonner mon nom de variable m_ archaïque)

 import java.io.IOException;
import java.io.OutputStream;

public class AutoFlushOutputStream extends OutputStream {

    protected long m_count = 0;
    protected long m_limit = 4096; 
    protected OutputStream m_out;

    public AutoFlushOutputStream(OutputStream out) {
        m_out = out;
    }

    public AutoFlushOutputStream(OutputStream out, long limit) {
        m_out = out;
        m_limit = limit;
    }

    public void write(int b) throws IOException {

        if (m_out != null) {
            m_out.write(b);
            m_count++;
            if (m_limit > 0 && m_count >= m_limit) {
                m_out.flush();
                m_count = 0;
            }
        }
    }
}
 

1voto

david a. Points 2969

Je ne sais pas non plus si flush() sur ServletOutputStream fonctionne dans ce cas, mais ServletResponse.flushBuffer() doit envoyer la réponse au client (au moins par spécification de servlet 2.3) .

ServletResponse.setBufferSize() semble également prometteur.

1voto

Kostas Points 11

Donc, suivant votre scénario, ne devriez-vous pas vider (ing) l'intérieur de cette boucle while (à chaque itération), au lieu d'en sortir? J'essaierais cela, avec un tampon un peu plus grand cependant.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X