Büyük veri dosyaları satır satır nasıl kopyalanır?


9

35GB'lık bir dosyam var CSV. Her satırı okumak ve bir koşulla eşleşiyorsa satırı yeni bir CSV'ye yazmak istiyorum.

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");
        });
    }
}

Bu yakl. 7 dakika. Bu süreci daha da hızlandırmak mümkün mü?


1
Evet, bunu Java'dan yapmayı deneyemezsiniz, bunun yerine doğrudan Linux / Windows / vb. işletim sistemi. Java yorumlanır ve kullanımında her zaman bir ek yük olacaktır. Bunun yanı sıra, hayır, hızlandırmanın açık bir yolu yok ve 35GB için 7 dakika benim için makul görünüyor.
Tim Biegeleisen

1
Belki paralleldaha hızlı yapar? Ve bu etraftaki çizgileri karıştırmıyor mu?
Thilo

1
Create BufferedWriterkullanarak, kendini yapıcısı sen tampon boyutunu ayarlamak sağlar. Belki daha büyük (veya daha küçük) bir tampon boyutu fark yaratacaktır. Ben BufferedWriterarabellek boyutu ile ana bilgisayar işletim sistemi arabellek boyutu eşleştirmek çalışacağız .
Abra

5
@TimBiegeleisen: "Java yorumlanır" en iyi şekilde yanıltıcıdır ve neredeyse her zaman yanlıştır. Evet, bazı optimizasyonlar için JVM dünyasından ayrılmanız gerekebilir, ancak bu daha hızlı Java'da yapmak kesinlikle yapılabilir.
Joachim Sauer

1
Hakkında bir şeyler yapabileceğiniz herhangi bir sıcak nokta olup olmadığını görmek için uygulamayı profillemelisiniz. Ham IO hakkında çok fazla şey yapamazsınız (varsayılan 8192 bayt arabelleği o kadar da kötü değildir, çünkü sektör boyutları vb. Söz konusudur), ancak (dahili olarak) yapabileceğiniz şeyler olabilir. birlikte çalışmak.
Kayaman

Yanıtlar:


4

Bu bir seçenekse, disk G / Ç'sini en aza indirmek için GZipInputStream / GZipOutputStream kullanabilirsiniz.

Files.newBufferedReader / Writer varsayılan tampon boyutu, 8 KB kullanmak inanıyorum. Daha büyük bir arabelleği deneyebilirsiniz.

String'e dönüştürülürken Unicode, yavaşlar (ve hafızanın iki katını kullanır). Kullanılan UTF-8, StandardCharsets.ISO_8859_1 kadar basit değil.

Bayt ile çalışabiliyorsanız en iyisiÇoğunlukla ve yalnızca belirli CSV alanları için bunları Dize'ye dönüştürürseniz iyi olur.

Bellek eşlemeli dosya en uygun dosya olabilir. Paralellik, dosya tükürerek dosya aralıklarında kullanılabilir.

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

Bu biraz fazla kod olacak, doğru çizgileri alacak (byte)'\n', ancak aşırı karmaşık olmayacak.


Bayt okuma ile ilgili problem, gerçek dünyada, çizginin başlangıcını, belirli bir karakterde alt dizeyi değerlendirmem ve satırın kalan kısmını sadece dış dosyaya yazmam gerektiğidir. Yani muhtemelen satırları sadece bayt olarak okuyamıyorum?
memberound

Sadece bir GZipInputStream + GZipOutputStreamramdiske tamamen hafızayı test ettim. Performans çok daha kötüydü ...
membersound

1
Gzip'te: o zaman yavaş bir disk değildir. Evet, bayt bir seçenektir: yeni satırlar, virgül, sekme, noktalı virgüllerin tümü bayt olarak işlenebilir ve Dize'den çok daha hızlı olacaktır. UTF-8'den UTF-16'ya kadar baytlar Dizeden UTF-8'e kadar baytlara.
Joop Eggen

1
Dosyanın farklı bölümlerini zaman içinde eşlemeniz yeterlidir. Sınıra ulaştığınızda, MappedByteBufferbilinen son iyi konumdan yeni bir tane oluşturun ( FileChannel.mapuzun sürer).
Joachim Sauer

1
2019'da kullanmaya gerek yok new RandomAccessFile(…).getChannel(). Sadece kullan FileChannel.open(…).
Holger

0

bunu deneyebilirsiniz:

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

Sanırım bu sizi bir iki dakikadan kurtaracak. test, arabellek boyutu belirtilerek yaklaşık 4 dakika içinde makinemde yapılabilir.

daha hızlı olabilir mi? bunu dene:

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);
    }
  }
}

Bu sizi üç veya dört dakika kurtarmalıdır.

Eğer bu hala yeterli değilse. (Soruyu muhtemelen sormanızın nedeni, görevi tekrar tekrar yürütmeniz gerektiğidir). bir dakika hatta birkaç saniye içinde bunu yapmak istiyorsanız. veriyi işlemeli ve db'ye kaydetmeli, sonra görevi birden çok sunucu tarafından işlemelisiniz.


Son örneğinize göre: cbufiçeriği nasıl değerlendirebilir ve yalnızca bölümleri yazabilirim? Arabellek dolduğunda sıfırlamam gerekir mi? (ara belleğin dolu olduğunu nasıl bilebilirim?)
Üye

0

Tüm önerileriniz sayesinde, en hızlı şekilde yazarı değiştirdim BufferedOutputStream, bu da yaklaşık% 25 iyileşme sağladı:

   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());
                    });
        }
    }

Hala benim durumumdan BufferedReaderdaha iyi performans gösterir BufferedInputStream.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.