2017-09-24 6 views
0

Voici une classe dont je dispose qui écrit un ConcurrentMap<String, List<String>> dans un fichier. La clé dans la carte est le chemin, et la valeur dans la carte doit être écrite séquentiellement dans le fichier. Ce Task<Void> est appelé à chaque fois qu'il ya 1000 valeurs dans la carte:Ouverture et fermeture multiples de fichiers Java pour l'écriture

public class MapWriter extends Task<Void> { 

private final ParsingProducerConsumerContext context; 

public MapWriter(ParsingProducerConsumerContext context) { 
    this.context = context; 
} 

@Override 
protected Void call() throws Exception { 
    if (!isCancelled() || !context.isEmpty()) { 
     ConcurrentMap<String, List<String>> jsonObjectMap = context.fetchAndReset(); 

     jsonObjectMap.entrySet().forEach((t) -> {     
      try { 
       FileUtils.writeLines(new File(context.getPath() + t.getKey() + "\\sorted.json"), t.getValue(), true); 
      } catch (IOException ex) { 
       context.getLogger().log("Error writing to disk:"); 
       context.getLogger().log(ex.toString()); 
       context.stopEverything(); 
      } 
     }); 

     context.getLogger().log(jsonObjectMap.values().stream().mapToInt(List::size).sum() + " schedules written to disk "); 
    } else { 
     context.getLogger().log("Nothing to write"); 
    } 

    return null; 
} 
} 

Pendant ce temps, cette tâche est en cours d'exécution, il y a un producteur Task lire une ligne de fichier ~ 2GByte par ligne, qui obtient traitée par un consommateur et placé dans ConcurrentMap<String, List<String>>.

Alors que cela ne fonctionne pas, c'est très lent! Mes recherches suggèrent que l'ouverture et la fermeture de fichiers sont assez importantes pour altérer les performances, alors je me demandais si l'approche suivante pourrait être meilleure?

Conserver un Map<String, File> de File objets qui sont ouverts. Si la clé ConcurrentMap<String, List<String>> correspond à un fichier ouvert, utilisez cette référence File pour écrire Lorsque tout le traitement est terminé, bouclez les valeurs Map<String, File> et fermez chaque fichier.

Cela vous semble-t-il judicieux? Il y aurait environ 100 dossiers ouverts cependant.

EDIT :: J'ai fait un benchmark simple en utilisant System.nanoTime(). Le fichier importé ligne par ligne par le producteur est d'environ 2 Go, et chaque ligne est comprise entre 6 et 10 Ko (dans le List<String>).

En outre, une erreur OutOfMemory est rencontrée! Je suppose que le 2GByte est effectivement chargé en mémoire, et ne pas être écrit assez rapidement?

514 jsonObjects written to disk in 2258007ms 538 jsonObjects written to disk in 2525166ms 1372 jsonObjects written to disk in 169959ms 1690 jsonObjects written to disk in 720824ms 9079 jsonObjects written to disk in 5221168ms 22552 jsonObjects written to disk in 6943207ms 13392 jsonObjects written to disk in 6475639ms 0 jsonObjects written to disk in 6ms 0 jsonObjects written to disk in 5ms 0 jsonObjects written to disk in 5ms 40 jsonObjects written to disk in 23108ms 631 jsonObjects written to disk in 200269ms 3883 jsonObjects written to disk in 2054177ms Producer failed with java.lang.OutOfMemoryError: GC overhead limit exceeded

Pour être complet, voici la classe Producteur:

public class NRODJsonProducer extends Task<Void> { 

private final ParsingProducerConsumerContext context; 

public NRODJsonProducer(ParsingProducerConsumerContext context) { 
    this.context = context; 
} 

@Override 
protected Void call() throws Exception { 
    context.getLogger().log("Producer created"); 

    LineIterator li = FileUtils.lineIterator(new File(context.getPath() + context.getFilterFile())); 

    while (li.hasNext()) { 
     try { 
      context.getQueue().put(li.next()); 
     } catch (InterruptedException ex) { 
      Logger.getLogger(NRODJsonProducer.class.getName()).log(Level.SEVERE, null, ex); 
     } 
    } 

    LineIterator.closeQuietly(li); 

    context.getLogger().log("Producer finished..."); 

    return null; 
} 

}

+0

Essayez-le et faites-le nous savoir. –

Répondre

0

Je ne vois pas pourquoi. Ce code écrit tout pour une clé d'un fichier portant le même nom, puis passe à la clé suivante. Si le producteur crée une autre entrée pour cette clé, il écrase l'entrée précédente et ce code réécrira le fichier. Garder les fichiers ouverts n'aidera pas cela. Le vrai problème semble être que vous continuez à écrire les mêmes données dans le fichier, car vous ne retirez jamais une clé traitée de la carte.

NB La condition d'utilisation est incorrecte. Il devrait être

if (!isCancelled() && !context.isEmpty()) 
+0

Ah ... quand 'context.fetchAndReset()' est appelé, cela récupère la carte du 'context'. La carte dans le contexte est remplacée par une nouvelle, donc tout le temps MapWriter l'a, il ne sera lu qu'à partir puis supprimé. – swshaun