Comment copier ligne par ligne de gros fichiers de données?

9

J'ai un CSVfichier de 35 Go . Je veux lire chaque ligne et écrire la ligne dans un nouveau CSV s'il correspond à une condition.

try (BufferedWriter writer = Files.newBufferedWriter(Paths.get("source.csv"))) {
    try (BufferedReader br = Files.newBufferedReader(Paths.get("target.csv"))) {
        br.lines().parallel()
            .filter(line -> StringUtils.isNotBlank(line)) //bit more complex in real world
            .forEach(line -> {
                writer.write(line + "\n");
        });
    }
}

Cela prend env. 7 minutes. Est-il possible d'accélérer encore plus ce processus?

membersound
la source
1
Oui, vous pouvez essayer de ne pas le faire à partir de Java, mais plutôt de le faire directement à partir de votre Linux / Windows / etc. système opérateur. Java est interprété et il y aura toujours un surcoût à l'utiliser. En plus de cela, non, je n'ai pas de moyen évident d'accélérer, et 7 minutes pour 35 Go me semblent raisonnables.
Tim Biegeleisen
1
Peut-être que supprimer le parallelrend plus rapide? Et cela ne change-t-il pas les lignes?
Thilo
1
Créez BufferedWritervous - même, en utilisant le constructeur qui vous permet de définir la taille du tampon. Peut-être qu'une taille de tampon plus grande (ou plus petite) fera une différence. J'essaierais de faire correspondre la BufferedWritertaille de la mémoire tampon à la taille de la mémoire tampon du système d'exploitation hôte.
Abra
5
@TimBiegeleisen: "Java est interprété" est au mieux trompeur et presque toujours faux également. Oui, pour certaines optimisations, vous devrez peut-être quitter le monde JVM, mais faire cela plus rapidement en Java est certainement faisable.
Joachim Sauer
1
Vous devez profiler l'application pour voir s'il existe des points d'accès sur lesquels vous pouvez faire quelque chose. Vous ne pourrez pas faire grand-chose sur les E / S brutes (le tampon par défaut de 8192 octets n'est pas si mauvais, car il y a des tailles de secteur, etc.), mais il peut y avoir des choses (en interne) que vous pourriez être en mesure de travailler avec.
Kayaman

Réponses:

4

S'il s'agit d'une option, vous pouvez utiliser GZipInputStream / GZipOutputStream pour minimiser les E / S de disque.

Files.newBufferedReader / Writer utilise une taille de tampon par défaut, 8 Ko je crois. Vous pouvez essayer un tampon plus grand.

La conversion en chaîne, Unicode, ralentit (et utilise deux fois la mémoire). L'UTF-8 utilisé n'est pas aussi simple que StandardCharsets.ISO_8859_1.

Le mieux serait si vous pouvez travailler avec des octets pour la plupart et uniquement pour des champs CSV spécifiques les convertir en chaîne.

Un fichier mappé en mémoire peut être le plus approprié. Le parallélisme peut être utilisé par des plages de fichiers, crachant le fichier.

try (FileChannel sourceChannel = new RandomAccessFile("source.csv","r").getChannel(); ...
MappedByteBuffer buf = sourceChannel.map(...);

Cela deviendra un peu beaucoup de code, pour obtenir des lignes droites (byte)'\n', mais pas trop complexes.

Joop Eggen
la source
Le problème avec la lecture des octets est que dans le monde réel, je dois évaluer le début de la ligne, sous-chaîne sur un caractère spécifique et écrire uniquement la partie restante de la ligne dans le fichier de sortie. Donc, je ne peux probablement pas lire les lignes uniquement en octets?
membersound
Je viens de tester GZipInputStream + GZipOutputStreamtotalement inmemory sur un ramdisk. La performance était bien pire ...
Membersound
1
Sur Gzip: alors ce n'est pas un disque lent. Oui, les octets sont une option: les sauts de ligne, les virgules, les tabulations, les points-virgules peuvent tous être traités comme des octets et seront considérablement plus rapides que sous forme de chaîne. Octets sous forme de caractères UTF-8 à UTF-16 en chaîne en UTF-8 en octets.
Joop Eggen
1
Mappez simplement différentes parties du fichier au fil du temps. Lorsque vous atteignez la limite, créez simplement un nouveau à MappedByteBufferpartir de la dernière position connue ( FileChannel.mapprend longtemps).
Joachim Sauer
1
En 2019, il n'est pas nécessaire d'utiliser new RandomAccessFile(…).getChannel(). Utilisez simplement FileChannel.open(…).
Holger
0

vous pouvez essayer ceci:

try (BufferedWriter writer = new BufferedWriter(new FileWriter(targetFile), 1024 * 1024 * 64)) {
  try (BufferedReader br = new BufferedReader(new FileReader(sourceFile), 1024 * 1024 * 64)) {

Je pense que cela vous fera gagner une ou deux minutes. le test peut être effectué sur ma machine en environ 4 minutes en spécifiant la taille du tampon.

cela pourrait-il être plus rapide? essaye ça:

final char[] cbuf = new char[1024 * 1024 * 128];

try (Writer writer = new FileWriter(targetFile)) {
  try (Reader br = new FileReader(sourceFile)) {
    int cnt = 0;
    while ((cnt = br.read(cbuf)) > 0) {
      // add your code to process/split the buffer into lines.
      writer.write(cbuf, 0, cnt);
    }
  }
}

Cela devrait vous faire économiser trois ou quatre minutes.

Si cela ne suffit toujours pas. (La raison pour laquelle je suppose que vous posez la question est probablement que vous devez exécuter la tâche à plusieurs reprises). si vous voulez le faire en une minute ou même quelques secondes. vous devez alors traiter les données et les enregistrer dans db, puis traiter la tâche par plusieurs serveurs.

user_3380739
la source
Jusqu'à votre dernier exemple: comment puis-je évaluer le cbufcontenu et écrire uniquement des portions? Et devrais-je réinitialiser le tampon une fois plein? (Comment puis-je savoir que le tampon est plein?)
Membersound
0

Grâce à toutes vos suggestions, le plus rapide que j'ai trouvé a été d'échanger avec l'écrivain BufferedOutputStream, ce qui a donné une amélioration d'environ 25%:

   try (BufferedReader reader = Files.newBufferedReader(Paths.get("sample.csv"))) {
        try (BufferedOutputStream writer = new BufferedOutputStream(Files.newOutputStream(Paths.get("target.csv")), 1024 * 16)) {
            reader.lines().parallel()
                    .filter(line -> StringUtils.isNotBlank(line)) //bit more complex in real world
                    .forEach(line -> {
                        writer.write((line + "\n").getBytes());
                    });
        }
    }

Pourtant, les BufferedReaderperformances sont meilleures que BufferedInputStreamdans mon cas.

membersound
la source