Java NIO FileChannel versus desempenho / utilidade do FileOutputstream

Eu estou tentando descobrir se há alguma diferença no desempenho (ou vantagens) quando usamos o nio FileChannel versus FileInputStream/FileOuputStream normal para ler e gravar arquivos no sistema de arquivos. Eu observei que na minha máquina ambos executam no mesmo nível, também muitas vezes a maneira FileChannel é mais lenta. Por favor, posso saber mais detalhes comparando esses dois methods. Aqui está o código que eu usei, o arquivo que estou testando tem cerca de 350MB . É uma boa opção usar classs baseadas em NIO para File I / O, se eu não estiver procurando por access random ou outros resources avançados?

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

Minha experiência com tamanhos de arquivos maiores é que o java.nio é mais rápido que o java.io Solidamente mais rápido. Como na faixa de> 250%. Dito isso, estou eliminando gargalos óbvios, que sugiro que seu micro-benchmark possa sofrer. Áreas potenciais para investigar:

O tamanho do buffer. O algoritmo que você basicamente tem é

  • copiar do disco para o buffer
  • copiar do buffer para o disco

Minha experiência pessoal é que esse tamanho de buffer está pronto para o ajuste. Eu estabeleci em 4KB para uma parte da minha aplicação, 256 KB para outra. Eu suspeito que seu código está sofrendo com um buffer tão grande. Execute alguns benchmarks com buffers de 1KB, 2KB, 4KB, 8KB, 16KB, 32KB e 64KB para provar isso a si mesmo.

Não execute benchmarks de java que leiam e escrevam no mesmo disco.

Se você fizer isso, então você está realmente comparando o disco, e não o Java. Eu também sugeriria que, se a sua CPU não estiver ocupada, provavelmente você está tendo algum outro gargalo.

Não use um buffer se você não precisa.

Por que copiar para a memory se o seu destino for outro disco ou uma NIC? Com arquivos maiores, a latência incurada não é trivial.

Como outros disseram, use FileChannel.transferTo() ou FileChannel.transferFrom() . A principal vantagem aqui é que a JVM usa o access do SO ao DMA ( Direct Memory Access ), se presente. (Isso depende da implementação, mas as versões modernas da Sun e da IBM em CPUs de uso geral estão prontas.) O que acontece é que os dados vão diretamente de / para o disco, para o barramento e depois para o destino … ignorando qualquer circuito RAM ou a CPU.

O aplicativo da web em que passei meus dias e minha noite trabalhando é muito pesado. Também fiz benchmarks micro e benchmarks do mundo real. E os resultados estão no meu blog, dê uma olhada:

  • Métricas de desempenho do mundo real: java.io vs. java.nio
  • Métricas de desempenho no mundo real: java.io vs. java.nio (The Sequel)

Use dados de produção e ambientes

Micro-benchmarks são propensos a distorção. Se puder, faça o esforço de coletar dados exatamente do que você planeja fazer, com a carga esperada, no hardware esperado.

Meus benchmarks são sólidos e confiáveis ​​porque ocorreram em um sistema de produção, um sistema robusto, um sistema sob carga, reunidos em toras. Não a unidade SATA de 7200 RPM do meu notebook, enquanto assistia intensamente enquanto a JVM trabalhava no meu disco rígido.

O que você está correndo? Importa.

Se o que você deseja comparar é o desempenho da cópia de arquivos, então, para o teste de canal, você deve fazer isso:

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

Isso não será mais lento do que armazenar em buffer de um canal para outro e será potencialmente mais rápido. De acordo com os Javadocs:

Muitos sistemas operacionais podem transferir bytes diretamente do cache do sistema de arquivos para o canal de destino, sem copiá-los.

Com base em meus testes (Win7 64 bits, 6 GB de RAM, Java6), o NIO transferFrom é rápido apenas com arquivos pequenos e se torna muito lento em arquivos maiores. O NIO databuffer flip sempre supera o padrão IO.

  • Copiando 1000x2MB

    1. NIO (transferFrom) ~ 2300 ms
    2. NIO (datababuffer direct 5000b flip) ~ 3500ms
    3. Padrão IO (buffer 5000b) ~ 6000 ms
  • Copiando 100x20mb

    1. NIO (datababuffer direct 5000b flip) ~ 4000ms
    2. NIO (transferFrom) ~ 5000ms
    3. Padrão IO (buffer 5000b) ~ 6500ms
  • Copiando 1x1000mb

    1. NIO (datababuffer direct 5000b flip) ~ 4500s
    2. Padrão IO (buffer 5000b) ~ 7000ms
    3. NIO (transferFrom) ~ 8000ms

O método transferTo () funciona em partes de um arquivo; não foi concebido como um método de cópia de arquivos de alto nível: Como copiar um arquivo grande no Windows XP?

Eu testei o desempenho de FileInputStream vs. FileChannel para decodificar arquivos codificados em base64. Em meus experimentos testei arquivos bastante grandes e o io tradicional era sempre um pouco mais rápido que o nio.

O FileChannel pode ter tido uma vantagem em versões anteriores do jvm devido à sobrecarga de synchronization em várias classs relacionadas ao io, mas o jvm moderno é muito bom na remoção de bloqueios desnecessários.

Respondendo a parte de “utilidade” da pergunta:

Uma pegadinha bastante sutil do uso do FileChannel sobre o FileOutputStream é que executar qualquer uma de suas operações de bloqueio (por exemplo, read() ou write() ) de um thread que está em estado interrompido fará com que o canal feche abruptamente com java.nio.channels.ClosedByInterruptException .

Agora, isso pode ser uma coisa boa se o FileChannel foi usado para fazer parte da function principal do thread, e design levou isso em conta.

Mas também pode ser irritante se usado por algum recurso auxiliar, como uma function de registro. Por exemplo, você pode encontrar sua saída de log subitamente fechada se a function de log for chamada por um thread que também é interrompido.

É lamentável que isso seja tão sutil, porque não explicar isso pode levar a erros que afetam a integridade da gravação. [1] [2]

Se você não estiver usando o recurso de transferênciaPara ou resources sem bloqueio, você não notará uma diferença entre o IO tradicional e o NIO (2) porque o IO tradicional é mapeado para o NIO.

Mas se você puder usar os resources da NIO, como transferFrom / To, ou quiser usar Buffers, é claro que o NIO é o caminho a ser seguido.

Minha experiência é que o NIO é muito mais rápido com arquivos pequenos. Mas quando se trata de arquivos grandes, FileInputStream / FileOutputStream é muito mais rápido.