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;
}
}
Essayez-le et faites-le nous savoir. –