2009-05-21 6 views
11

Je lis dans un fichier texte en utilisant FileInputStream qui place le contenu du fichier dans un tableau d'octets. Je convertis ensuite le tableau d'octets en une chaîne en utilisant new String (octet).Analyse de fichier texte en Java

Une fois que j'ai la chaîne j'utilise String.split("\n") pour diviser le fichier en un tableau String, puis en prenant ce tableau de chaînes et en l'analysant en faisant un String.split(",") et maintenez le contenu dans un Arraylist.

J'ai un fichier 200MB + et il manque de mémoire lorsque je démarre la JVM avec 1 Go de mémoire. Je sais que je dois faire quelque chose correctement quelque part, je ne suis pas sûr si la façon dont je suis l'analyse est incorrecte ou la structure de données que j'utilise.

Cela me prend aussi environ 12 secondes pour analyser le fichier, ce qui me semble être beaucoup de temps. Est-ce que quelqu'un peut indiquer ce que je fais peut-être qui me fait perdre la mémoire et ce qui peut ralentir mon programme?

Le contenu du regard de fichier comme indiqué ci-dessous:

"12334", "100", "1.233", "TEST", "TEXT", "1234" 
"12334", "100", "1.233", "TEST", "TEXT", "1234" 
. 
. 
. 
"12334", "100", "1.233", "TEST", "TEXT", "1234" 

Merci

+0

Veuillez clarifier: Dites-vous que vous utilisez -Xmx1024m pour démarrer votre machine virtuelle Java et que vous obtenez toujours une erreur OutOfMemoryError? – duffymo

+0

Utiliser un modèle compilé serait mieux (même lancer votre propre groupe de spécialistes). Mais il semble que vous ayez juste beaucoup de données d'objet. –

+0

duffymo, c'est exactement le cas. Je suis en train de régler la JVM avec le -Xmx1024m et obtenir hte OutOfMemoryError –

Répondre

8

On dirait que vous faites quelque chose de mal à moi - une création tout objet Lotta passe.

Quelle est la représentativité de ce fichier "test"? Que faites-vous vraiment avec ces données? Si c'est typique de ce que vous avez vraiment, je dirais qu'il y a beaucoup de répétitions dans ces données.

Si tout se passe dans Strings, commencez par un BufferedReader pour lire chaque ligne. Pré-allouer cette liste à une taille proche de ce dont vous avez besoin pour ne pas gaspiller de ressources à chaque fois. Diviser chacune de ces lignes à la virgule; assurez-vous de supprimer les doubles guillemets.

Vous pourriez vous demander: "Pourquoi ai-je besoin de tout ce fichier en mémoire en même temps?" Pouvez-vous lire un peu, traiter un peu, et ne jamais avoir le tout en mémoire à la fois? Vous seul connaissez assez bien votre problème pour y répondre.

Peut-être que vous pouvez lancer jvisualvm si vous avez JDK 6 et voir ce qui se passe avec la mémoire. Ce serait un excellent indice.

+0

La façon dont le questionneur est en train de le faire semble créer un grand char [] (dans une chaîne) et ensuite des chaînes qui sont des tranches de cela, ce qui est étonnamment la manière efficace de le faire. (Implémentation non vérifiée de la division Bien sûr, tout dépend de l'implémentation.) –

+0

Vous avez raison sur "uber efficient", Tom. Mon conseil ferait en sorte que cela empire. Si le problème persiste, je pense que c'est le traitement à la volée et jvisualvm qui aidera le plus. – duffymo

+0

Maintenant que nous avons des flux avec Java 8, je me demande si cela peut être fait plus efficacement en utilisant la programmation fonctionnelle. C'est pour cela que les cours d'eau sont nés. – duffymo

2

Si vous avez 200 000 000 de fichiers de caractères et que vous les divisez tous les cinq caractères, vous disposez de 40 000 000 objets String. Supposons qu'ils partagent des données de caractères réelles avec l'original 400 Mo String (char est 2 octets). Un String est dit 32 octets, soit 1 280 000 000 octets de String objets.

(Il est probablement intéressant de noter que c'est la mise en œuvre très dépendante. split pourrait créer entièrement des chaînes avec tout nouveau soutien char[] ou, OTOH, partager quelques String communes valeurs. Certaines implémentations Java de ne pas utiliser le découpage en tranches de char[]. Certains peuvent utiliser une forme compacte de type UTF-8 et donne des temps d'accès aléatoires très pauvres.)

Même en supposant des chaînes plus longues, c'est beaucoup d'objets. Avec autant de données, vous voudrez probablement travailler avec la plus grande partie sous forme compacte comme l'original (seulement avec des index). Convertissez uniquement en objets dont vous avez besoin. L'implémentation doit être similaire à la base de données (même si, traditionnellement, elle ne gère pas efficacement les chaînes de longueur variable).

4

Il semble que vous ayez actuellement 3 copies du fichier entier en mémoire: le tableau d'octets, la chaîne et le tableau des lignes. Au lieu de lire les octets dans un tableau d'octets et de les convertir en caractères à l'aide de new String(), il serait préférable d'utiliser un InputStreamReader, qui convertira en caractères de manière incrémentielle, plutôt que tous les éléments initiaux.

En outre, au lieu d'utiliser String.split ("\ n") pour obtenir les lignes individuelles, vous devez lire une ligne à la fois. Vous pouvez utiliser la méthode readLine() dans BufferedReader.

Essayez quelque chose comme ceci:

BufferedReader reader = new BufferedReader(new InputStreamReader(fileInputStream, "UTF-8")); 
try { 
    while (true) { 
    String line = reader.readLine(); 
    if (line == null) break; 
    String[] fields = line.split(","); 
    // process fields here 
    } 
} finally { 
    reader.close(); 
} 
+0

La façon originale dont les Strings (devraient) tous partagent le même backing char [], et donc être plus efficace. Une division de ligne n'est probablement pas trop mauvaise, car il n'y aura qu'un seul caractère [] par ligne. –

+0

(Et le tableau d'octets n'a pas besoin d'être en mémoire en même temps que le tableau de lignes.) –

+0

Je commençais à avoir l'impression d'avoir de nombreuses copies du contenu du fichier en mémoire. Je vais essayer cela et voir la différence –

11

Je ne suis pas sûr de savoir comment il est efficace mémoire sage, mais ma première approche serait d'utiliser un Scanner car il est incroyablement facile à utiliser:

File file = new File("/path/to/my/file.txt"); 
Scanner input = new Scanner(file); 

while(input.hasNext()) { 
    String nextToken = input.next(); 
    //or to process line by line 
    String nextLine = input.nextLine(); 
} 

input.close(); 

Vérifiez l'API pour savoir comment modifier le délimiteur utilisé pour séparer les jetons.

5

Jetez un coup d'oeil à ces pages. Ils contiennent de nombreux analyseurs CSV open source. JSaPar en fait partie.

+0

Une suggestion particulière? –

+0

Eh bien, je suis un peu partial ici puisque je suis l'auteur de la bibliothèque JSaPar. C'est pourquoi je l'ai mentionné dans ma réponse, mais l'une des autres bibliothèques pourrait mieux vous convenir selon le problème que vous essayez de résoudre. – stenix

0

Tout en appelant/invoquant votre programme, vous pouvez utiliser cette commande: java [-options] className [...] args
à la place de [-options] fournir plus de mémoire, par exemple -Xmx1024m ou plus. mais c'est juste une solution de contournement, vous devez changer votre mécanisme d'analyse syntaxique.

Questions connexes