2010-03-08 4 views
1

Je rencontre les erreurs suivantes lorsque j'essaie de stocker un fichier volumineux dans une chaîne.Copie d'un fichier texte Java dans une chaîne

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 
    at java.util.Arrays.copyOf(Arrays.java:2882) 
    at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:100) 
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:515) 
    at java.lang.StringBuffer.append(StringBuffer.java:306) 
    at rdr2str.ReaderToString.main(ReaderToString.java:52) 

Comme il est évident, je suis à court d'espace tas. Fondamentalement, mon pgm ressemble à quelque chose comme ça. Est-ce que quelqu'un peut me suggérer pourquoi je cours dans l'erreur d'espace de tas? Merci.

+0

Quelle est la taille du fichier? Quels sont les paramètres de votre mémoire JVM -Xms, -Xmx? Des paramètres -XX? – shoover

+0

C'est environ 30MB. Cela pourrait être quelque chose de plus grand que ça. Fondamentalement, ce que j'ai écrit est un client qui consomme un webservice. Le client passe la chaîne en tant qu'argument au service Web. Je n'ai actuellement pas changé mes paramètres JVM. – Deepak

Répondre

1

Dans l'OP, votre programme s'interrompt pendant que le StringBuffer est en cours de développement. Vous devriez préallouer cela à la taille dont vous avez besoin ou du moins à proximité. Lorsque StringBuffer doit se développer, il a besoin de RAM pour la capacité d'origine et la nouvelle capacité. Comme TomTom l'a dit aussi, votre fichier est probablement composé de 8 bits, il sera donc converti en unicode 16 bits en mémoire, ce qui fera doubler sa taille.

Le programme n'a même pas encore rencontré le prochain doublement - qui est StringBuffer.toString() en Java 6 allouera une nouvelle String et la char[] interne seront copiées à nouveau (dans certaines versions antérieures de Java cela n'a pas été le cas). Au moment de cette copie vous aurez besoin de doubler l'espace de tas - donc à ce moment au moins 4 fois la taille réelle de vos fichiers (30MB * 2 pour byte-> unicode, puis 60MB * 2 pour toString() call = 120MB) . Une fois cette méthode terminée, GC nettoiera les classes temporaires.

Si vous ne pouvez pas augmenter l'espace mémoire de votre programme, vous aurez des difficultés. Vous ne pouvez pas prendre la route "facile" et renvoyer simplement un String. Vous pouvez essayer de le faire de manière incrémentielle afin de ne pas avoir à vous soucier de la taille du fichier (l'une des meilleures solutions). Regardez votre code de service Web dans le client. Il peut fournir un moyen d'utiliser une autre classe que String - peut-être un java.io.Reader, java.lang.CharSequence, ou une interface spéciale, comme le SAX org.xml.sax.InputSource. Chacun d'entre eux peut être utilisé pour créer une classe d'implémentation qui lit à partir de votre fichier en morceaux, car les appelants en ont besoin au lieu de charger le fichier entier en une seule fois. Par exemple, si vos routes de gestion de service Web peuvent prendre un CharSequence alors (si elles sont bien écrites), vous pouvez créer un gestionnaire spécial pour renvoyer un seul caractère à la fois du fichier - mais mettre en tampon l'entrée. Voir cette question similaire: How to deal with big strings and limited memory.

1

Par défaut, Java commence avec un très petit tas maximum (64M sous Windows). Est-il possible que vous essayiez de lire un fichier trop grand?

Si oui, vous pouvez augmenter le tas avec le paramètre JVM -Xmx256M (pour définir tas maximum 256 Mo)

J'ai essayé de courir une version légèrement modifiée de votre code:

public static void main(String[] args) throws Exception{ 
    FileReader fr = new FileReader("<filepath>"); 
    StringBuffer sb = new StringBuffer(); 
    char[] b = new char[1000]; 
    int n = 0; 
    while ((n = fr.read(b)) > 0) 
     sb.append(b, 0, n);  

    String fileString = sb.toString(); 
    System.out.println(fileString); 
} 

sur une petite fichier (2 Ko) et cela a fonctionné comme prévu. Vous devrez définir le paramètre JVM.

+0

@Kris. Merci. Mon prog fonctionne aussi pour les petits fichiers. C'est juste que je ne peux pas modifier les options JVM que ce morceau de code va dans un client qui accepte les fichiers de taille variable et devrait idéalement les convertir en chaîne, les passer à un service web. – Deepak

2
  • Vous allouez un petit StringBuffer de plus en plus long. Preallocate en fonction de la taille du fichier, et vous serez également beaucoup plus rapide. Notez que java est Unicode, la chaîne probablement pas, donc vous utilisez ... deux fois la taille de la mémoire.

  • Selon VM (32 bits? 64 bits?) Et les limites définies (http://www.devx.com/tips/Tip/14688), il se peut que vous n'ayez pas assez de mémoire disponible. Quelle est la taille du fichier?

+0

Le fichier fait environ 30 Mo.J'ai essayé de pré-allouer le StringBuffer avec la taille de fichier en octets, mais il ne serait pas allouer autant. – Deepak

+1

@Deepak: si vous ne pouvez pas pré-allouer à la taille attendue, vous ne pouvez certainement pas le lire de façon incrémentielle. Vous devez augmenter la taille du tas (et comme TomTom l'a indiqué, ce sera au moins le double, donc votre fichier de 30 Mo nécessitera au moins 60 Mo d'espace mémoire). Notez que lorsque vous convertissez cela en une chaîne ('StringBuffer.toString()'), de nombreuses implémentations en cours créent une nouvelle chaîne, ce qui signifie que vous avez besoin de doubler à nouveau (c'est-à-dire 120 Mo d'espace mémoire). Ou, vous pouvez le faire de façon progressive. –

4

Vous manquez de mémoire car la manière dont vous avez écrit votre programme nécessite le stockage de l'ensemble du fichier arbitrairement volumineux en mémoire. Vous avez 2 options:

  • Vous pouvez augmenter la mémoire par des commutateurs en passant la ligne de commande à la machine virtuelle Java:

    java -Xms<initial heap size> -Xmx<maximum heap size> 
    
  • Vous pouvez réécrire votre logique afin qu'il traite des données de fichiers comme des flux , gardant ainsi l'empreinte mémoire de votre programme faible.

Je recommande la deuxième option. C'est plus de travail mais c'est la bonne façon d'y aller.

EDIT: Pour déterminer votre paramétrage par défaut du système pour la taille de tas initiale et max, vous pouvez utiliser cet extrait de code (que je stole from a JavaRanch thread):

public class HeapSize {  
    public static void main(String[] args){  
     long kb = 1024; 
     long heapSize = Runtime.getRuntime().totalMemory();  
     long maxHeapSize = Runtime.getRuntime().maxMemory(); 
     System.out.println("Heap Size (KB): " + heapSize/1024); 
     System.out.println("Max Heap Size (KB): " + maxHeapSize/1024); 
    }  
} 
+0

Heap Size (Ko): 81280 Taille maximale du tas (Ko): 83392 – Deepak

+0

@Asaph - Le problème avec la définition de la taille de pile est que j'utilise ce code dans un client qui consomme un service Web. Le client prend un fichier, le convertit en chaîne et le transmet au webservice. Donc, je doute de l'utilité de l'option JVM. Merci. – Deepak

+0

Je serais intéressé à en savoir plus sur la deuxième option cependant. Suggérez-vous une sorte de mécanisme de synchronisation? – Deepak

1

Bien que cela pourrait ne pas résoudre votre problème, certaines petites choses que vous pouvez faire pour rendre votre code un peu mieux:

  • créer votre StringBuffer avec une capacité initiale de la taille de la chaîne que vous lisez
  • fermer votre FileReader à la fin: fr.close();
1

Essayer de lire un fichier arbitrairement volumineux dans la mémoire principale d'une application est une mauvaise conception. Période. Aucun ajustement des paramètres de la JVM/etc ... ne résoudra le problème principal ici. Je vous recommande de faire une pause et de faire des recherches sur la façon de traiter les flux en java - voici un bon tutorial et voici un autre good tutorial pour vous aider à démarrer.

Questions connexes