Java NIO FileChannel versus FileOutputstream performance / Utility

j'essaie de comprendre s'il y a une différence dans les performances (ou les avantages) lorsque nous utilisons nio FileChannel versus normal FileInputStream/FileOuputStream pour lire et écrire des fichiers dans le système de fichiers. J'ai observé que sur ma machine les deux performent au même niveau, aussi plusieurs fois la voie FileChannel est plus lente. Puis-je avoir plus de détails en comparant ces deux méthodes? Voici le code que j'ai utilisé, le fichier que je teste est d'environ 350MB . Est-ce une bonne option d'utiliser les classes basées sur NIO pour Fichier I / O, Si Je ne suis pas à la recherche de l'accès aléatoire ou d'autres fonctionnalités avancées?

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");
    }
}
159
demandé sur Narendra Pathai 2009-10-22 10:21:25

7 réponses

mon expérience avec les plus grandes tailles de fichiers a été que java.nio est plus rapide que java.io . plus vite. comme dans la gamme >250%. Cela dit, je suis en train d'éliminer les goulets d'étranglement évidents, dont je pense que votre micro-benchmark pourrait souffrir. Les domaines potentiels d'enquête:

la taille du tampon. l'algorithme que vous avez essentiellement est

  • copie du disque au tampon
  • copier du tampon au disque

ma propre expérience a été que cette taille de tampon est mûr pour le réglage. J'ai réglé sur 4KB pour une partie de ma demande, 256KB pour une autre. Je soupçonne que votre code souffre d'un si grand tampon. Exécutez quelques repères avec des tampons de 1KB, 2KB, 4KB, 8KB, 16KB, 32KB et 64KB pour le prouver à vous-même.

Ne pas exécutez des benchmarks java qui lisent et écrivent sur le même disque.

si vous le faites, alors vous comparez vraiment le disque, et pas Java. Je suggère également que si votre CPU n'est pas occupé, alors vous rencontrez probablement un autre goulot d'étranglement.

N'utilisez pas de tampon si vous n'en avez pas besoin.

pourquoi copier en mémoire si votre cible est un autre disque ou un NIC? Avec des fichiers plus grands, la latence incured est non-trivial.

comme les autres ont dit, utilisez FileChannel.transferTo() ou FileChannel.transferFrom() . L'avantage clé est que la JVM utilise l'accès du système D'exploitation à DMA ( Direct Memory Access ), s'il est présent. (cela dépend de la mise en œuvre, mais les versions modernes Sun et IBM sur les CPU à usage général sont bonnes à aller.) Ce qui se passe est que les données va tout droit vers/à partir du disque, au bus, puis à la destination... contournement de n'importe quel circuit par le biais de la RAM ou le CPU.

l'application web sur laquelle j'ai travaillé jour et nuit est très lourde. J'ai fait des micro-benchmarks et des benchmarks du monde réel aussi. Et les résultats sont sur mon blog, ont un look-voir:

utiliser les données de production et les environnements

Micro-benchmarks sont sujettes à distorsion. Si vous le pouvez, faites l'effort de collecter des données à partir d'exactement ce que vous comptez faire, avec la charge que vous attendez, le matériel que vous attendez.

mes benchmarks sont solides et fiables parce qu'ils ont eu lieu sur un système de production, un système beefy, un système sous charge, réunis en rondins. Pas mon le lecteur SATA 7200 RPM 2,5" de notebook pendant que je regardais intensément le JVM travailler mon disque dur.

Qu'est-ce que tu manigances? C'questions.

190
répondu Stu Thompson 2016-08-22 16:42:29

si la chose que vous voulez comparer est la performance de copie de fichier, alors pour le test de canal vous devriez faire ceci à la place:

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

ce ne sera pas plus lent que de se tamponner d'un canal à l'autre, et sera potentiellement massivement plus rapide. Selon les Javadocs:

de nombreux systèmes d'exploitation peuvent transférer des octets directement du cache du système de fichiers vers le canal cible sans les copier.

35
répondu uckelman 2014-06-30 17:46:41

basé sur mes tests (Win7 64bit, 6 Go RAM, Java6), NIO transfer from est rapide seulement avec les petits fichiers et devient très lent sur les fichiers plus grands. NIO databuffer flip surpasse toujours la norme IO.

  • Copie 1000x2MB

    1. NIO (transfer from) ~2300ms
    2. NIO (direct datababuffer 5000b flip) ~3500ms
    3. Standard IO (buffer 5000b) ~6000ms
  • Copie 100x20mb

    1. NIO (direct datababuffer 5000b flip) ~4000ms
    2. NIO (transfer from) ~5000ms
    3. Standard IO (buffer 5000b) ~6500ms
  • Copie 1x1000mb

    1. NIO (direct datababuffer 5000b flip) ~4500s
    2. Standard IO (buffer 5000b) ~7000ms
    3. NIO (transfer from) ~8000ms

la méthode transferTo() fonctionne sur des morceaux d'un fichier; n'était pas prévue comme une méthode de copie de fichier de haut niveau: comment copier un grand fichier dans Windows XP?

7
répondu noxo 2017-05-23 11:46:55

j'ai testé la performance de FileInputStream vs. FileChannel pour le décodage des fichiers encodés base64. Dans mes expériences, j'ai testé un assez grand fichier et io traditionnel était toujours un peu plus rapide que nio.

FileChannel aurait pu avoir un avantage dans les versions précédentes de la jvm en raison de la synchronisation au-dessus de plusieurs classes liées à io, mais les jvm modernes sont assez bons pour enlever les écluses inutiles.

3
répondu Jörn Horstmann 2009-10-22 19:58:51

répondant à la partie" utilité "de la question:

un gotcha plutôt subtil de l'utilisation de FileChannel sur FileOutputStream est que l'exécution de l'une de ses opérations de blocage (par exemple read() ou write() ) à partir d'un fil qui est dans état interrompu provoquera la fermeture abrupte du canal avec java.nio.channels.ClosedByInterruptException .

maintenant, cela pourrait être une bonne chose si quelque soit le FileChannel était utilisé pour fait partie de la fonction principale du thread, et le design en a tenu compte.

mais il pourrait aussi être embêtant s'il est utilisé par une caractéristique auxiliaire telle qu'une fonction de journalisation. Par exemple, vous pouvez trouver votre sortie de journalisation soudainement fermée si la fonction de journalisation est appelée par un thread qui est aussi interrompu.

il est regrettable que ce soit si subtil parce que ne pas en tenir compte peut conduire à des bogues qui affectent l'intégrité d'écriture. [1][2]

3
répondu antak 2017-02-23 07:36:35

si vous n'utilisez pas la fonctionnalité transferTo ou des fonctionnalités non-bloquantes, vous ne remarquerez pas de différence entre l'IO traditionnel et NIO(2) parce que l'IO traditionnel correspond à NIO.

mais si vous pouvez utiliser les fonctionnalités de NIO comme transfer from/To ou si vous voulez utiliser des tampons, alors bien sûr NIO est la voie à suivre.

2
répondu eckes 2013-04-22 16:57:54

mon expérience est, que NIO est beaucoup plus rapide avec de petits fichiers. Mais quand il s'agit de gros fichiers FileInputStream/FileOutputStream est beaucoup plus rapide.

-1
répondu tangens 2009-10-22 06:26:56