web-dev-qa-db-fra.com

Java FileChannel entre NIO et performances / utilité de FileOutputstream

J'essaie de comprendre s'il y a une différence de performances (ou d'avantages) lorsque nous utilisons nio FileChannel par rapport à la normale FileInputStream/FileOuputStream pour lire et écrire des fichiers sur le système de fichiers. J'ai observé que sur ma machine, les deux fonctionnent au même niveau, et que plusieurs fois le chemin FileChannel est plus lent. Puis-je s'il vous plaît connaître plus de détails comparant ces deux méthodes. Voici le code que j'ai utilisé, le fichier que je teste est autour de 350MB. Est-ce une bonne option d’utiliser des classes basées sur NIO pour les E/S sur fichiers, si je ne cherche pas un 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");
    }
}
164
Keshav

Mon expérience avec des fichiers de plus grande taille a été que Java.nio Est plus rapide que Java.io. Solidement plus rapide. Comme dans la plage> 250%. Cela dit, j'élimine les goulots d'étranglement évidents, ce dont votre micro-référence pourrait souffrir. Domaines potentiels d’enquête:

La taille de la mémoire tampon. L’algorithme que vous avez en principe est

  • copier du disque vers le tampon
  • copier du tampon sur le disque

Ma propre expérience a été que cette taille de tampon est mûre pour le réglage. J'ai choisi 4 Ko pour une partie de mon application et 256 Ko pour une autre. Je soupçonne que votre code souffre d'un tampon aussi volumineux. Exécuter des tests avec des tampons de 1 Ko, 2 Ko, 4 Ko, 8 Ko, 16 Ko, 32 Ko et 64 Ko pour le prouver à vous-même.

N'exécutez pas de tests Java qui lisent et écrivent sur le même disque.

Si vous le faites, vous comparez réellement le disque, et non Java. Je suggérerais également que si votre processeur n'est pas occupé, 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 une carte réseau? Avec des fichiers plus volumineux, la latence induite est non négligeable.

Comme d’autres l’ont dit, utilisez FileChannel.transferTo() ou FileChannel.transferFrom(). Le principal avantage ici est que la machine virtuelle utilise l'accès du système d'exploitation à DMA ( accès direct à la mémoire ), le cas échéant. (Cela dépend de l'implémentation, mais les versions modernes de Sun et IBM sur les processeurs à usage général sont bonnes à utiliser.) ... en ignorant tout circuit passant par RAM ou par la CPU.

L’application Web sur laquelle je passe mes journées et mes nuits est très lourdeIO. J'ai également réalisé des micro-tests et des tests dans le monde réel. Et les résultats sont en hausse sur mon blog, regardez:

Utiliser les données et les environnements de production

Les micro-repères sont sujets à la distorsion. Si vous le pouvez, efforcez-vous de rassembler des données correspondant exactement à ce que vous prévoyez de faire, avec la charge que vous attendez, sur le matériel que vous attendez.

Mes points de repère sont solides et fiables car ils se sont déroulés sur un système de production, un système costaud, un système sous charge rassemblé dans des bûches. Not ​​Le disque SATA de 7200 tr/min 2.5 "de mon ordinateur portable alors que je regardais intensément la machine virtuelle travailler sur mon disque dur.

Qu'est-ce que tu cours? Cela compte.

194
Stu Thompson

Si vous souhaitez comparer les performances de la copie de fichiers, procédez comme suit pour le test de canal:

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

Cela ne sera pas plus lent que de vous tamponner d'un canal à l'autre et sera potentiellement beaucoup plus rapide. Selon les Javadocs:

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

38
uckelman

D'après mes tests (Win7 64 bits, 6 Go de RAM, Java6), NIO transferFrom n'est rapide qu'avec de petits fichiers et devient très lent pour des fichiers plus volumineux. Le retournement du tampon de données NIO surpasse toujours les E/S standard.

  • Copie 1000x2Mo

    1. NIO (transfertDe) ~ 2300ms
    2. NIO (flip de datababuffer direct 5000b) ~ 3500ms
    3. Standard IO (tampon 5000b) ~ 6000ms
  • Copier 100 x 20 Mo

    1. NIO (flip de datababuffer direct 5000b) ~ 4000ms
    2. NIO (transfertDe) ~ 5000ms
    3. Standard IO (tampon 5000b) ~ 6500ms
  • Copie 1x1000mb

    1. NIO (flip de datababuffer direct 5000b) ~ 4500s
    2. Standard IO (tampon 5000b) ~ 7000ms
    3. NIO (transferFrom) ~ 8000ms

La méthode transferTo () fonctionne sur les morceaux d'un fichier; n'était pas conçu comme une méthode de copie de fichier de haut niveau: Comment copier un fichier volumineux dans Windows XP?

7
noxo

Répondre à la partie "utilité" de la question:

L’utilisation de FileChannel sur FileOutputStream est plutôt subtile, c’est que l’une de ses opérations de blocage (par exemple, read() ou write()) à partir d’un thread qui se trouve dans état interromp entraînera la fermeture brutale du canal avec Java.nio.channels.ClosedByInterruptException .

Maintenant, cela pourrait être une bonne chose si tout ce que FileChannel était utilisé faisait partie de la fonction principale du thread, et si le design en tenait compte.

Mais il pourrait également être embêtant s'il est utilisé par une fonction 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 également interrompu.

Il est regrettable que cela soit si subtil parce que ne pas en tenir compte peut conduire à des bogues affectant l’intégrité en écriture.[1] [2]

5
antak

J'ai testé les performances de FileInputStream par rapport à FileChannel pour le décodage de fichiers codés en base64. Lors de mes expériences, j’ai testé un fichier assez volumineux et le io traditionnel était toujours un peu plus rapide que nio.

FileChannel aurait pu avoir un avantage dans les versions précédentes de jvm en raison de la surcharge de synchronisation liée à plusieurs classes liées à io, mais les jvm modernes sont assez efficaces pour supprimer les verrous inutiles.

3
Jörn Horstmann

Si vous n'utilisez pas la fonctionnalité transferTo ou des fonctionnalités non bloquantes, vous ne remarquerez aucune différence entre le IO traditionnel et le NIO (2), car le IO traditionnel est mappé sur NIO.

Mais si vous pouvez utiliser les fonctionnalités de NIO telles que transferFrom/To ou si vous souhaitez utiliser des tampons, alors bien sûr, NIO est la voie à suivre.

2
eckes

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

0
tangens