C # bir bayt dizisi içine büyük bir dosyayı okumak için en iyi yolu?


391

Bayt dizilerine büyük ikili dosyaları (birkaç megabayt) okuyacak bir web sunucum var. Sunucu aynı anda birkaç dosya (farklı sayfa istekleri) okuma olabilir, bu yüzden CPU çok fazla vergi vermeden bunu yapmak için en iyi şekilde arıyorum. Aşağıdaki kod yeterince iyi mi?

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

60
Örneğiniz kısaltılabilir byte[] buff = File.ReadAllBytes(fileName).
Jesse C. Slicer

3
Neden bir üçüncü taraf web hizmeti olmak, dosyanın akış yerine web hizmetine gönderilmeden önce dosyanın tam RAM'de olması gerektiğini ima ediyor? Web hizmeti farkı bilmiyor.
Brian

@Brian, Bazı istemciler, örneğin Java gibi bir .NET akışının nasıl işleneceğini bilmiyor. Bu durumda, yapılabilecek tek şey bayt dizisindeki tüm dosyayı okumaktır.
sjeffrey

4
@sjeffrey: Verilerin bir .NET akışı olarak aktarılmaması, akışının sağlanması gerektiğini söyledim. Müşteriler farkı her iki şekilde de bilemezler.
Brian

Yanıtlar:


776

Sadece her şeyi şu şekilde değiştirin:

return File.ReadAllBytes(fileName);

Eğer bellek tüketimi hakkında endişe varsa Ancak, gereken değil tüm tek seferde en belleğe tüm dosyayı okuyun. Bunu parçalar halinde yapmalısın.


40
bu yöntem 2 ^ 32 baytlık dosyalarla (4.2 GB)
sınırlıdır

11
File.ReadAllBytes OutOfMemoryException dosyasını büyük dosyalarla atıyor (630 MB dosya ile test edildi ve başarısız oldu)
Sakito

6
@ juanjo.arana Evet, elbette ... elbette her zaman belleğe sığmayan bir şey olacak, bu durumda sorunun cevabı yok. Genel olarak, dosyayı akışa almalı ve tamamen belleğe kaydetmemelisiniz. Bir stopgap ölçüsü için buna bakmak isteyebilirsiniz: msdn.microsoft.com/en-us/library/hh285054%28v=vs.110%29.aspx
Mehrdad Afshari

4
.NET'te dizi boyutu için bir sınır vardır, ancak .NET 4.5'te
illegal -immigrant

3
@harag Hayır, sorunun sorduğu şey bu değil.
Mehrdad Afshari

72

Buradaki cevabın genellikle "yapma" olduğunu söyleyebilirim . Tüm verilere bir kerede kesinlikle ihtiyacınız olmadığı sürece , Streamtabanlı bir API (veya okuyucu / yineleyicinin bir çeşidi) kullanmayı düşünün . Yani özellikle sistem yükünü azaltmak ve veri maksimize etmek (soru önerdiği gibi) birden çok paralel işlemleri olduğunda önemli.

Örneğin, bir arayan kişiye veri akışı yapıyorsanız:

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

3
İfadenize eklemek için, istemciye bir dosya akışı gibi bir G / Ç ilişkili işleminiz varsa zaman uyumsuz ASP.NET işleyicilerini de düşünmenizi öneririm. Ancak, tüm dosyayı bir nedenden dolayı okumak zorundaysanızbyte[] , akışları veya başka bir şeyi kullanmamanızı ve sistem tarafından sağlanan API'yi kullanmanızı öneririm.
Mehrdad Afshari

@Mehrdad - kabul etti; ancak tüm bağlam net değil. Benzer şekilde MVC'nin bunun için eylem sonuçları vardır.
Marc Gravell

Evet, tüm verilere aynı anda ihtiyacım var. Üçüncü taraf bir web servisine gidiyor.
Tony_Henrich

Sistem tarafından sağlanan API nedir?
Tony_Henrich

1
@Tony: Ben cevap belirtildiği: File.ReadAllBytes.
Mehrdad Afshari

32

Bunu düşünürdüm:

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

3
Gerçekten büyük dosyalar alırken bunun durabileceğini unutmayın.
vapcguy

28

Kodunuz buna göre hesaplanabilir (File.ReadAllBytes yerine):

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

Read yöntemi tarafından yerleştirilen Integer.MaxValue - dosya boyutu sınırlamasına dikkat edin. Başka bir deyişle, aynı anda yalnızca 2 GB'lık bir yığın okuyabilirsiniz.

Ayrıca, FileStream öğesinin son bağımsız değişkeninin bir arabellek boyutu olduğuna dikkat edin.

Ayrıca FileStream ve BufferedStream hakkında okuma öneririm .

Her zaman olduğu gibi en hızlı profil oluşturmak için basit bir örnek program en faydalı olacaktır.

Ayrıca, temel donanımınızın performans üzerinde büyük etkisi olacaktır. Büyük önbellekli sunucu tabanlı sabit disk sürücüleri ve yerleşik bellek önbelleğine sahip bir RAID kartı mı kullanıyorsunuz? Yoksa IDE bağlantı noktasına bağlı standart bir sürücü mü kullanıyorsunuz?


Donanım türü neden fark yaratsın? Yani IDE ise, bir .NET yöntemi ve RAID ise başka bir yöntem mi kullanıyorsunuz?
Tony_Henrich

@Tony_Henrich - Programlama dilinizden yaptığınız aramalarla hiçbir ilgisi yoktur. Farklı tipte sabit disk sürücüleri vardır. Örneğin, Seagate diskleri "AS" veya "NS" olarak sınıflandırılır, NS sunucu tabanlı, büyük önbellek sürücüsüdür; burada "AS" sürücüsü tüketici-ev bilgisayarı tabanlı disktir. Arama hızları ve dahili aktarım hızları, bir şeyi diskten ne kadar hızlı okuyabileceğinizi de etkiler. RAID dizileri, önbellekleme yoluyla okuma / yazma performansını önemli ölçüde artırabilir. Böylece dosyayı bir kerede okuyabilirsiniz, ancak temel donanım hala belirleyici faktördür.

2
Bu kod kritik bir hata içeriyor. Okuma yalnızca en az 1 bayt döndürmek için gereklidir.
mafu

Ben uzun gibi int döküm böyle kontrol yapı ile sarmak emin olun: ekose desenli ((int) fs.Length)
tzup

Sadece var binaryReader = new BinaryReader(fs); fileData = binaryReader.ReadBytes((int)fs.Length);bu usingaçıklamada yapardım . Ama bu OP'nin yaptığı gibi etkili bir şekilde, sadece uzunluğun değerini almak ve dönüştürmek yerine döküm fs.Lengthyaparak bir kod satırı kestim . intlongFileInfo
vapcguy

9

İşlemlerin sıklığına, dosyaların boyutuna ve baktığınız dosya sayısına bağlı olarak, dikkate alınması gereken başka performans sorunları da vardır. Hatırlanması gereken bir şey, her bayt dizinizin çöp toplayıcısının merhametinde serbest bırakılacağıdır. Bu verilerin hiçbirini önbelleğe almıyorsanız, çok fazla çöp oluşturabilir ve performansınızın çoğunu GC'deki% Time'a kaybedebilirsiniz.. Parçalar 85K'dan büyükse, serbest bırakmak için tüm nesillerin bir koleksiyonunu gerektiren Büyük Nesne Yığını'na (LOH) tahsis edeceksiniz (bu çok pahalıdır ve bir sunucuda devam ederken tüm yürütmeyi durduracaktır. ). Ek olarak, LOH üzerinde bir ton nesneniz varsa, düşük performansa ve bellek dışı istisnalara yol açan LOH parçalanması (LOH asla sıkıştırılmaz) ile sonuçlanabilir. Belli bir noktaya geldiğinizde süreci geri dönüştürebilirsiniz, ancak bunun en iyi uygulama olup olmadığını bilmiyorum.

Mesele şu ki, tüm baytları belleğe mümkün olan en hızlı şekilde okumadan önce uygulamanızın tam yaşam döngüsünü düşünmelisiniz veya genel performans için kısa vadeli performansla ticaret yapıyor olabilirsiniz.


Bu konuda kaynak kodu C #, yönetmek için garbage collector, chunks, performans, etkinlik sayaçları , ...
PreguntonCojoneroCabrón

6

Ben iyi olduğunu söyleyebilirim BinaryReader, ancak tampon uzunluğu almak için tüm bu kod satırları yerine, bu yeniden düzenlenebilir:

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

Kullanmaktan daha iyi olmalı .ReadAllBytes(), çünkü .ReadAllBytes()yorumculardan birinin 600 MB'lık dosyalarla ilgili sorunları olduğunu içeren en üst yanıttaki yorumlarda gördüğümden , çünkü BinaryReaderbu tür bir şey için tasarlanmıştır. Ayrıca, bir usingifadeye koymak FileStreamve BinaryReaderkapalı ve atılmasını sağlar.


C # için, "(FileStream fs = new File.OpenRead (fileName))" yerine "using (FileStream fs = File.OpenRead (fileName))" kullanmanız gerekir. File.OpenRead ()
Syed Mohamed'den

@Syed Yukarıdaki kod C # için yazılmıştı, ama haklısın, neworada gerekli değildi. Kaldırıldı.
vapcguy

1

'Büyük bir dosya' 4GB sınırını aşarsa, aşağıdaki yazılı kod mantığım uygundur. Dikkat edilmesi gereken en önemli husus, SEEK yöntemiyle kullanılan UZUN veri türüdür. Bir UZUN 2 ^ 32 veri sınırlarını aşabildiğinden. Bu örnekte, kod ilk olarak büyük dosyayı 1 GB'lık parçalar halinde işliyor, büyük 1 GB'lık büyük parçalar işlendikten sonra, kalan (<1GB) baytlar işleniyor. Ben 4GB boyutu ötesinde dosyaların CRC hesaplanması ile bu kodu kullanın. ( bu örnekte crc32c hesaplaması için https://crc32c.machinezoo.com/ adresini kullanarak )

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

0

Performansı artırmak için C # 'daki BufferedStream sınıfını kullanın. Bir arabellek, verileri önbelleğe almak için kullanılan ve böylece işletim sistemine yapılan çağrıların sayısını azaltan bellekte bir bayt bloğudur. Tamponlar okuma ve yazma performansını artırır.

Kod örneği ve ek açıklama için aşağıdakilere bakın: http://msdn.microsoft.com/en-us/library/system.io.bufferedstream.aspx


Her BufferedStreamşeyi aynı anda okurken bir kullanmanın anlamı nedir ?
Mehrdad Afshari

Dosyayı bir kerede okumamak için en iyi performansı istedi.
Todd Moses

9
Performans, bir operasyon bağlamında ölçülebilir. Bir kerede ard arda okuduğunuz bir akış için belleğe ek arabelleğe almanın fazladan bir arabellekten fayda sağlaması olası değildir.
Mehrdad Afshari

0

bunu kullan:

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

2
Stack Overflow'a hoş geldiniz! Açıklamalar bu platformdaki cevapların önemli bir parçası olduğundan, lütfen kodunuzu ve sorunun sorunun nasıl çözüldüğünü ve neden diğer yanıtlardan daha iyi olabileceğini açıklayın. İyi bir cevap yazma kılavuzumuz sizin için yararlı olabilir. Teşekkürler
David

-4

Ben Response.TransferFile()sonra a Response.Flush()ve Response.End()büyük dosyalarınızı sunmak için yöntemi denemenizi tavsiye ederim .


-7

2 GB'ın üzerindeki dosyalarla ilgileniyorsanız, yukarıdaki yöntemlerin başarısız olduğunu göreceksiniz.

Akışı MD5'e vermek ve dosyanızı sizin için yığınlamasına izin vermek çok daha kolaydır :

private byte[] computeFileHash(string filename)
{
    MD5 md5 = MD5.Create();
    using (FileStream fs = new FileStream(filename, FileMode.Open))
    {
        byte[] hash = md5.ComputeHash(fs);
        return hash;
    }
}

11
Kodun soruyla (veya yazılı metinde ne önerdiğinizle) ilgili olduğunu görmüyorum
Vojtech B
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.