Java NIO FileChannel ve FileOutputstream performansı / kullanışlılığı


169

Dosya sistemine dosya okumak ve yazmak için nio'ya FileChannelkarşı nio kullandığımızda performansta (veya avantajlarda) herhangi bir fark olup olmadığını anlamaya çalışıyorum FileInputStream/FileOuputStream. Makinemde hem aynı seviyede hem de FileChannelyolun daha yavaş çalıştığını gözlemledim . Bu iki yöntemi karşılaştırarak daha fazla ayrıntı öğrenebilir miyim? İşte kullandığım kod, test ettiğim dosya etrafında 350MB. Rastgele erişime veya diğer gelişmiş özelliklere bakmıyorsam Dosya G / Ç için NIO tabanlı sınıfları kullanmak iyi bir seçenek mi?

package trialjavaprograms;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class JavaNIOTest {
    public static void main(String[] args) throws Exception {
        useNormalIO();
        useFileChannel();
    }

    private static void useNormalIO() throws Exception {
        File file = new File("/home/developer/test.iso");
        File oFile = new File("/home/developer/test2");

        long time1 = System.currentTimeMillis();
        InputStream is = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(oFile);
        byte[] buf = new byte[64 * 1024];
        int len = 0;
        while((len = is.read(buf)) != -1) {
            fos.write(buf, 0, len);
        }
        fos.flush();
        fos.close();
        is.close();
        long time2 = System.currentTimeMillis();
        System.out.println("Time taken: "+(time2-time1)+" ms");
    }

    private static void useFileChannel() throws Exception {
        File file = new File("/home/developer/test.iso");
        File oFile = new File("/home/developer/test2");

        long time1 = System.currentTimeMillis();
        FileInputStream is = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(oFile);
        FileChannel f = is.getChannel();
        FileChannel f2 = fos.getChannel();

        ByteBuffer buf = ByteBuffer.allocateDirect(64 * 1024);
        long len = 0;
        while((len = f.read(buf)) != -1) {
            buf.flip();
            f2.write(buf);
            buf.clear();
        }

        f2.close();
        f.close();

        long time2 = System.currentTimeMillis();
        System.out.println("Time taken: "+(time2-time1)+" ms");
    }
}

5
transferTo/ transferFromdosyaları kopyalamak için daha geleneksel olurdu. Hangi teknik sabit sürücünüzü daha hızlı veya daha yavaş yapmamalıdır, ancak bir seferde küçük parçaları okur ve kafanın arama yapmak için aşırı miktarda zaman harcamasına neden olursa bir sorun olabilir.
Tom Hawtin - tackline

1
(Hangi işletim sistemini kullandığınızı veya hangi JRE satıcısını ve sürümünü kullandığınızdan bahsetmezsiniz.)
Tom Hawtin - tackline

Hata! Sun JDK6 ile FC10 kullanıyorum.
Keshav

Yanıtlar:


202

Daha büyük dosya boyutları ile yaşadığım deneyim, daha java.niohızlıydı java.io. Çok daha hızlı. >% 250 aralığında olduğu gibi. Bununla birlikte, mikro ölçütünüzden muzdarip olabileceğini önerdiğim bariz darboğazları ortadan kaldırıyorum. Potansiyel araştırma alanları:

Arabellek boyutu. Temel olarak sahip olduğunuz algoritma

  • diskten arabelleğe kopyala
  • arabellekten diske kopyala

Benim kendi deneyimim, bu tampon boyutunun ayar için olgun olmasıydı. Uygulamamın bir kısmı için 4KB'ye, diğeri için 256KB'ye yerleştim. Kodunuzun böyle büyük bir arabellekle çekildiğinden şüpheleniyorum. Kendinizi kanıtlamak için 1KB, 2KB, 4KB, 8KB, 16KB, 32KB ve 64KB arabellekleriyle bazı ölçütler çalıştırın.

Aynı diske okuma ve yazma yapan java kıyaslamaları yapmayın.

Bunu yaparsanız, Java'yı değil, gerçekten diski karşılaştırıyorsunuz demektir. Ayrıca CPU'nuz meşgul değilse, muhtemelen başka bir darboğaz yaşamanızı öneririm.

İhtiyacınız yoksa tampon kullanmayın.

Hedefiniz başka bir disk veya NIC ise neden belleğe kopyalanmalı? Daha büyük dosyalarda, ortaya çıkan gecikme önemsiz değildir.

Diğerleri gibi, FileChannel.transferTo()veya kullanın FileChannel.transferFrom(). Buradaki en önemli avantaj, JVM'nin işletim sisteminin varsa DMA'ya ( Doğrudan Bellek Erişimi ) erişimini kullanmasıdır . (Bu uygulamaya bağlıdır, ancak genel amaçlı CPU'lardaki modern Sun ve IBM sürümleri uygundur.) Olan şey, veriler doğrudan diske, otobüse ve sonra hedefe gider ... herhangi bir devreyi atlayarak RAM veya CPU.

Benim gün ve gece üzerinde çalışırken geçirdiğim web uygulaması çok IO ağır. Mikro kriterler ve gerçek dünya kriterleri de yaptım. Ve sonuçlar blogumda, bir göz atın:

Üretim verilerini ve ortamlarını kullanma

Mikro kriterler bozulmaya eğilimlidir. Mümkünse, beklediğiniz yük ile beklediğiniz donanım üzerinde tam olarak yapmayı planladığınız şeyden veri toplamak için çaba gösterin.

Testlerim sağlam ve güvenilirdir çünkü bir üretim sisteminde, etli bir sistemde, yük altında bir sistemde günlüklerde toplandılar. Değil Notebook 7200 RPM 2.5" SATA sürücü JVM iş olarak yoğun benim hard disk izlerken.

Ne üzerinde koşuyorsun? Fark eder, önemi var.


@Stu Thompson - Gönderiniz için teşekkürler. Aynı konuda araştırma yaptığımdan beri cevabınıza rastladım. Java programcılarına nio maruz OS iyileştirmeleri anlamaya çalışıyorum. Bunlardan birkaçı - DMA ve bellek eşlemeli dosyalar. Bu tür iyileştirmelerle daha fazla karşılaştınız mı? PS - Blog bağlantılarınız bozuk.
Andy Dufresne

@AndyDufresne blogum şu anda kapalı, bu hafta ileride olacak - taşıma sürecinde.
Stu Thompson


1
Dosyaları bir dizinden diğerine kopyalamaya ne dersiniz? (Her farklı disk sürücüsü)
Deckard


38

Karşılaştırmak istediğiniz şey dosya kopyalamanın performansı ise, kanal testi için bunu yapmalısınız:

final FileInputStream inputStream = new FileInputStream(src);
final FileOutputStream outputStream = new FileOutputStream(dest);
final FileChannel inChannel = inputStream.getChannel();
final FileChannel outChannel = outputStream.getChannel();
inChannel.transferTo(0, inChannel.size(), outChannel);
inChannel.close();
outChannel.close();
inputStream.close();
outputStream.close();

Bu, kendinizi bir kanaldan diğerine tamponlamaktan daha yavaş olmayacak ve potansiyel olarak çok daha hızlı olacaktır. Javadocs'a göre:

Birçok işletim sistemi, gerçekte kopyalamadan baytları doğrudan dosya sistemi önbelleğinden hedef kanala aktarabilir.


7

Testlerime dayanarak (Win7 64bit, 6GB RAM, Java6), NIO transferiFrom sadece küçük dosyalarla hızlıdır ve daha büyük dosyalarda çok yavaşlar. NIO veri tabanı çeviricisi her zaman standart G / Ç'den daha iyi performans gösterir.

  • 1000x2MB kopyalama

    1. NIO (transferFrom) ~ 2300ms
    2. NIO (doğrudan veri tabanı 5000b çevirme) ~ 3500ms
    3. Standart IO (tampon 5000b) ~ 6000ms
  • 100x20mb kopyalanıyor

    1. NIO (doğrudan veri tabanı 5000b çevirme) ~ 4000ms
    2. NIO (transferFrom) ~ 5000 ms
    3. Standart IO (tampon 5000b) ~ 6500ms
  • Kopyalama 1x1000mb

    1. NIO (doğrudan veri tabanı 5000b çevirme) ~ 4500s
    2. Standart IO (tampon 5000b) ~ 7000ms
    3. NIO (transferFrom) ~ 8000ms

TransferTo () yöntemi bir dosyanın parçaları üzerinde çalışır; düzeyli bir dosya kopyalama yöntemi olarak tasarlanmamıştır: Windows XP'de büyük bir dosya nasıl kopyalanır?


6

Sorunun "kullanışlılığı" bölümünü yanıtlamak:

Kullanılmasının bir çok ince gotcha FileChannelüzerinde FileOutputStreambloke edici işlemler (örneğin herhangi bir performans olduğunu read()ya da write()bir diş ile ilgili) içerisinde en olduğu kesintili durumuna yakın aniden ile kanal neden olur java.nio.channels.ClosedByInterruptException.

Şimdi, ne için FileChannelkullanılırsa kullanılsın, iş parçacığının ana işlevinin bir parçasıysa ve tasarım bunu dikkate alırsa, bu iyi bir şey olabilir .

Ancak, bir kayıt işlevi gibi bazı yardımcı özellikler tarafından kullanılırsa sinir bozucu olabilir. Örneğin, günlüğe kaydetme işlevi de yarıda kesilmiş bir iş parçacığı tarafından çağrılırsa günlüğe kaydetme çıktısını aniden kapalı bulabilirsiniz.

Bunun çok incelikli olması talihsiz bir durum çünkü bunu hesaba katmamak yazma bütünlüğünü etkileyen hatalara yol açabilir. [1] [2]


3

Base64 kodlu dosyaları çözmek için FileInputStream ve FileChannel performansını test ettim. Deneyimlerimde oldukça büyük bir dosyayı test ettim ve geleneksel io, nio'dan biraz daha hızlıydı.

FileChannel, birkaç io ile ilgili sınıfta senkoklaştırma yükü nedeniyle jvm'nin önceki sürümlerinde bir avantaj sağlamış olabilir, ancak modern jvm, gereksiz kilitleri kaldırma konusunda oldukça iyidir.


2

TransferTo özelliğini veya engellemeyen özellikleri kullanmıyorsanız, geleneksel IO NIO ile eşleştiğinden geleneksel IO ve NIO (2) arasında bir fark görmezsiniz.

Ancak transferFrom / To gibi NIO özelliklerini kullanabiliyorsanız veya Tamponları kullanmak istiyorsanız, elbette NIO gitmenin yoludur.


0

Deneyimlerim, NIO'nun küçük dosyalarla daha hızlı olması. Ancak büyük dosyalar söz konusu olduğunda FileInputStream / FileOutputStream çok daha hızlıdır.


4
Bunu karıştırdın mı? Benim kendi deneyimim, java.niodaha büyük dosyalarda daha java.ioküçük değil , daha hızlı olması .
Stu Thompson

Hayır, deneyimim tam tersi. java.niodosya belleğe eşlenecek kadar küçük olduğu sürece hızlıdır. Daha büyük olursa (200 MB ve daha fazla) java.iodaha hızlıdır.
tangens

Vay. Tam tersim. Bir dosyayı okumak için eşlemeniz gerekmediğini unutmayın FileChannel.read(). Kullanarak dosyaları okumak için tek bir yaklaşım yoktur java.nio.
Stu Thompson

2
@tangens bunu kontrol ettin mi?
sinedsem
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.