web-dev-qa-db-fra.com

Meilleure façon de lire un fichier volumineux dans un tableau d'octets en C #?

J'ai un serveur Web qui lit les gros fichiers binaires (plusieurs mégaoctets) dans des tableaux d'octets. Le serveur peut lire plusieurs fichiers en même temps (différentes requêtes de page), je recherche donc le moyen le plus optimisé de le faire sans trop solliciter le processeur. Le code ci-dessous est-il suffisant?

public byte[] FileToByteArray(string fileName)
{
    byte[] buff = null;
    FileStream fs = new FileStream(fileName, 
                                   FileMode.Open, 
                                   FileAccess.Read);
    BinaryReader br = new BinaryReader(fs);
    long numBytes = new FileInfo(fileName).Length;
    buff = br.ReadBytes((int) numBytes);
    return buff;
}
364
Tony_Henrich

Remplacez simplement le tout par:

return File.ReadAllBytes(fileName);

Cependant, si vous êtes préoccupé par la consommation de mémoire, vous devriez ne pas lire le fichier entier en mémoire en une seule fois. Vous devriez le faire en morceaux.

739
Mehrdad Afshari

Je pourrais soutenir que la réponse ici est généralement est "ne pas". Sauf si vous avez absolument besoin de toutes les données en même temps, envisagez d'utiliser un API basé sur Stream- (ou une variante de lecteur/itérateur). Cela est particulièrement important lorsque vous effectuez plusieurs opérations parallèles (comme suggéré par la question) afin de minimiser la charge du système et d'optimiser le débit.

Par exemple, si vous transmettez des données en continu à un appelant:

Stream dest = ...
using(Stream source = File.OpenRead(path)) {
    byte[] buffer = new byte[2048];
    int bytesRead;
    while((bytesRead = source.Read(buffer, 0, buffer.Length)) > 0) {
        dest.Write(buffer, 0, bytesRead);
    }
}
64
Marc Gravell

Je penserais ceci:

byte[] file = System.IO.File.ReadAllBytes(fileName);
31
Powerlord

Votre code peut être factorisé à ceci (au lieu de File.ReadAllBytes):

public byte[] ReadAllBytes(string fileName)
{
    byte[] buffer = null;
    using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
    {
        buffer = new byte[fs.Length];
        fs.Read(buffer, 0, (int)fs.Length);
    }
    return buffer;
} 

Notez la limite Integer.MaxValue - taille de fichier placée par la méthode Read. En d'autres termes, vous ne pouvez lire qu'un morceau de 2 Go à la fois.

Notez également que le dernier argument de FileStream est une taille de mémoire tampon.

Je suggérerais également de lire à propos de FileStream et BufferedStream .

Comme toujours, un exemple de programme simple pour établir le profil le plus rapide sera le plus bénéfique.

De plus, votre matériel sous-jacent aura un impact important sur les performances. Utilisez-vous des disques durs de serveur avec des caches volumineux et une carte RAID avec un cache de mémoire intégré? Ou utilisez-vous un lecteur standard connecté au port IDE?

25
user113476

Selon la fréquence des opérations, la taille des fichiers et le nombre de fichiers que vous consultez, vous devez prendre en compte d'autres problèmes de performances. Une chose à retenir est que chacun de vos tableaux d'octets sera libéré à la merci du ramasse-miettes. Si vous ne cachez aucune de ces données, vous risquez de créer beaucoup de déchets et de perdre la majeure partie de vos performances: % Time in GC . Si les morceaux sont supérieurs à 85 Ko, vous allouerez au tas d'objets volumineux (LOH) qui nécessitera une collection de toutes les générations à libérer (ceci est très coûteux, et sur un serveur, toute exécution sera interrompue pendant son exécution. ). De plus, si vous avez une tonne d'objets sur LOH, vous pouvez vous retrouver avec une fragmentation de LOH (la LOH n'est jamais compactée), ce qui entraîne des performances médiocres et des exceptions de mémoire insuffisante. Vous pouvez recycler le processus une fois que vous avez atteint un certain point, mais je ne sais pas si c'est une bonne pratique.

Le fait est que vous devez prendre en compte le cycle de vie complet de votre application avant de simplement lire tous les octets en mémoire de la manière la plus rapide possible. Sinon, vous pourriez échanger des performances à court terme contre des performances globales.

9
Joel

Je dirais que BinaryReader est correct, mais peut être modifié en conséquence, au lieu de toutes ces lignes de code pour obtenir la longueur du tampon:

public byte[] FileToByteArray(string fileName)
{
    byte[] fileData = null;

    using (FileStream fs = File.OpenRead(fileName)) 
    { 
        using (BinaryReader binaryReader = new BinaryReader(fs))
        {
            fileData = binaryReader.ReadBytes((int)fs.Length); 
        }
    }
    return fileData;
}

Devrait être mieux que d'utiliser .ReadAllBytes(), puisque j'ai vu dans les commentaires sur la réponse du haut qui inclut .ReadAllBytes() qu'un des commentateurs avait des problèmes avec les fichiers> 600 Mo, car un BinaryReader signifie pour ce genre de chose. De plus, en le mettant dans une instruction using, vous assurez que les variables FileStream et BinaryReader sont fermées et supprimées.

6
vapcguy

Si le terme "fichier volumineux" dépasse la limite de 4 Go, la logique de code écrite suivante est appropriée. Le problème clé à noter est le type de données LONG utilisé avec la méthode SEEK. En tant que LONG, il est possible de pointer au-delà de 2 ^ 32 limites de données. Dans cet exemple, le code traite d'abord le fichier volumineux en morceaux de 1 Go. Après le traitement des morceaux entiers de 1 Go, les octets restants (<1 Go) sont traités. J'utilise ce code pour calculer le CRC des fichiers au-delà de la taille de 4 Go. (en utilisant https://crc32c.machinezoo.com/ pour le calcul de crc32c dans cet exemple)

private uint Crc32CAlgorithmBigCrc(string fileName)
{
    uint hash = 0;
    byte[] buffer = null;
    FileInfo fileInfo = new FileInfo(fileName);
    long fileLength = fileInfo.Length;
    int blockSize = 1024000000;
    decimal div = fileLength / blockSize;
    int blocks = (int)Math.Floor(div);
    int restBytes = (int)(fileLength - (blocks * blockSize));
    long offsetFile = 0;
    uint interHash = 0;
    Crc32CAlgorithm Crc32CAlgorithm = new Crc32CAlgorithm();
    bool firstBlock = true;
    using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
    {
        buffer = new byte[blockSize];
        using (BinaryReader br = new BinaryReader(fs))
        {
            while (blocks > 0)
            {
                blocks -= 1;
                fs.Seek(offsetFile, SeekOrigin.Begin);
                buffer = br.ReadBytes(blockSize);
                if (firstBlock)
                {
                    firstBlock = false;
                    interHash = Crc32CAlgorithm.Compute(buffer);
                    hash = interHash;
                }
                else
                {
                    hash = Crc32CAlgorithm.Append(interHash, buffer);
                }
                offsetFile += blockSize;
            }
            if (restBytes > 0)
            {
                Array.Resize(ref buffer, restBytes);
                fs.Seek(offsetFile, SeekOrigin.Begin);
                buffer = br.ReadBytes(restBytes);
                hash = Crc32CAlgorithm.Append(interHash, buffer);
            }
            buffer = null;
        }
    }
    //MessageBox.Show(hash.ToString());
    //MessageBox.Show(hash.ToString("X"));
    return hash;
}
1
Menno de Ruiter

utilisez ceci:

 bytesRead = responseStream.ReadAsync(buffer, 0, Length).Result;
0
Hardik Raval

Utilisez la classe BufferedStream en C # pour améliorer les performances. Un tampon est un bloc d'octets en mémoire utilisé pour mettre en cache des données, réduisant ainsi le nombre d'appels au système d'exploitation. Les tampons améliorent les performances de lecture et d'écriture.

Voir ci-dessous un exemple de code et une explication supplémentaire: http://msdn.Microsoft.com/en-us/library/system.io.bufferedstream.aspx

0
Todd Moses