Cách tốt nhất để đọc một tệp lớn thành một mảng byte trong C #?


391

Tôi có một máy chủ web sẽ đọc các tệp nhị phân lớn (vài megabyte) thành các mảng byte. Máy chủ có thể đang đọc một số tệp cùng một lúc (các yêu cầu trang khác nhau), vì vậy tôi đang tìm cách tối ưu nhất để thực hiện việc này mà không đánh thuế CPU quá nhiều. Mã dưới đây có đủ tốt không?

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
Ví dụ của bạn có thể được viết tắt byte[] buff = File.ReadAllBytes(fileName).
Jesse C. Choper

3
Tại sao nó là một dịch vụ web của bên thứ ba ngụ ý rằng tệp cần phải có đầy đủ RAM trước khi được gửi đến dịch vụ web, thay vì truyền phát? Các dịch vụ web sẽ không biết sự khác biệt.
Brian

@Brian, Một số khách hàng không biết cách xử lý luồng .NET, chẳng hạn như Java. Khi đây là trường hợp tất cả những gì có thể được thực hiện là đọc toàn bộ tệp trong mảng byte.
sjeffrey

4
@sjeffrey: Tôi đã nói dữ liệu nên được truyền phát, không được truyền dưới dạng luồng .NET. Các khách hàng sẽ không biết sự khác biệt.
Brian

Câu trả lời:


776

Chỉ cần thay thế toàn bộ bằng:

return File.ReadAllBytes(fileName);

Tuy nhiên, nếu bạn lo lắng về mức tiêu thụ bộ nhớ, bạn không nên đọc toàn bộ tệp vào bộ nhớ cùng một lúc. Bạn nên làm điều đó trong khối.


40
phương pháp này được giới hạn ở 2 ^ 32 byte tệp (4.2 GB)
Mahmoud Farahat

11
File.Read ALLBytes ném OutOfMemoryException với các tệp lớn (đã thử nghiệm với tệp 630 MB và không thành công)
sakito

6
@ juanjo.arana Vâng, tất nhiên ... tất nhiên sẽ luôn có thứ gì đó không phù hợp với bộ nhớ, trong trường hợp đó, không có câu trả lời cho câu hỏi. Nói chung, bạn nên truyền phát tệp và không lưu trữ nó trong bộ nhớ hoàn toàn. Bạn có thể muốn xem xét điều này để biết biện pháp ngăn chặn: msdn.microsoft.com/en-us/l Library / hh285054% 28v = vs.110% 29.aspx
Mehrdad Afshari 13/03/13

4
Có giới hạn về kích thước mảng trong .NET, nhưng trong .NET 4.5, bạn có thể bật hỗ trợ cho các mảng lớn (> 2GB) bằng tùy chọn cấu hình đặc biệt, xem msdn.microsoft.com/en-us/l Library / hh285054.aspx
bất hợp pháp di dân

3
@harag Không, và đó không phải là những gì câu hỏi yêu cầu.
Mehrdad Afshari

72

Tôi có thể lập luận rằng câu trả lời ở đây nói chung là "không". Trừ khi bạn thực sự cần tất cả dữ liệu cùng một lúc, hãy xem xét sử dụng StreamAPI dựa trên (hoặc một số biến thể của trình đọc / trình lặp). Điều đó đặc biệt quan trọng khi bạn có nhiều hoạt động song song (như được đề xuất bởi câu hỏi) để giảm thiểu tải hệ thống và tối đa hóa thông lượng.

Ví dụ: nếu bạn đang truyền dữ liệu đến người gọi:

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
Để thêm vào tuyên bố của bạn, tôi thậm chí còn đề nghị xem xét các trình xử lý ASP.NET không đồng bộ nếu bạn có thao tác ràng buộc I / O như truyền phát tệp đến máy khách. Tuy nhiên, nếu bạn phải đọc toàn bộ tệp byte[]vì một lý do nào đó, tôi khuyên bạn nên tránh sử dụng luồng hoặc bất cứ điều gì khác và chỉ sử dụng API do hệ thống cung cấp.
Mehrdad Afshari

@Mehrdad - đồng ý; nhưng bối cảnh đầy đủ không rõ ràng. Tương tự như vậy MVC có kết quả hành động cho việc này.
Marc Gravell

Có tôi cần tất cả dữ liệu cùng một lúc. Nó sẽ đến một dịch vụ web của bên thứ ba.
Tony_Henrich

API được cung cấp hệ thống là gì?
Tony_Henrich

1
@Tony: Tôi đã nêu trong câu trả lời của mình : File.ReadAllBytes.
Mehrdad Afshari

32

Tôi sẽ nghĩ điều này:

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

3
Lưu ý rằng điều này có thể bị đình trệ khi nhận được các tập tin thực sự lớn.
vapcguy

28

Mã của bạn có thể được xác định theo điều này (thay cho File.Read ALLBytes):

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

Lưu ý Integer.MaxValue - giới hạn kích thước tệp được đặt theo phương thức Đọc. Nói cách khác, bạn chỉ có thể đọc một đoạn 2 GB cùng một lúc.

Cũng lưu ý rằng đối số cuối cùng đối với FileStream là kích thước bộ đệm.

Tôi cũng sẽ đề nghị đọc về FileStreamBufferedStream .

Như mọi khi một chương trình mẫu đơn giản để hồ sơ nhanh nhất sẽ có lợi nhất.

Ngoài ra phần cứng cơ bản của bạn sẽ có ảnh hưởng lớn đến hiệu suất. Bạn có đang sử dụng ổ đĩa cứng dựa trên máy chủ với bộ nhớ cache lớn và thẻ RAID với bộ nhớ cache trên bo mạch không? Hay bạn đang sử dụng một ổ đĩa tiêu chuẩn được kết nối với cổng IDE?


Tại sao loại phần cứng sẽ làm cho một sự khác biệt? Vì vậy, nếu đó là IDE, bạn sử dụng một số phương thức .NET và nếu là RAID, bạn có sử dụng phương thức khác không?
Tony_Henrich

@Tony_Henrich - Nó không liên quan gì đến những gì bạn gọi từ ngôn ngữ lập trình của bạn. Có nhiều loại ổ đĩa cứng khác nhau. Ví dụ: các ổ đĩa Seagate được phân loại là "AS" hoặc "NS" với NS là ổ đĩa bộ nhớ cache lớn dựa trên máy chủ, trong đó ổ đĩa "AS" là ổ đĩa dựa trên máy tính gia đình. Tìm kiếm tốc độ và tốc độ truyền nội bộ cũng ảnh hưởng đến tốc độ bạn có thể đọc thứ gì đó từ đĩa. Mảng RAID có thể cải thiện đáng kể hiệu năng đọc / ghi thông qua bộ nhớ đệm. Vì vậy, bạn có thể có thể đọc tất cả các tệp cùng một lúc, nhưng phần cứng cơ bản vẫn là yếu tố quyết định.

2
Mã này có chứa một lỗi nghiêm trọng. Đọc chỉ cần trả lại ít nhất 1 byte.
mafu

Tôi sẽ đảm bảo gói dài đến int cast với cấu trúc đã kiểm tra như thế này: đã kiểm tra ((int) fs.Lipse)
tzup

Tôi sẽ chỉ làm var binaryReader = new BinaryReader(fs); fileData = binaryReader.ReadBytes((int)fs.Length);trong usingtuyên bố đó . Nhưng đó thực sự giống như những gì OP đã làm, chỉ cần tôi cắt ra một dòng mã bằng cách fs.Lengthsử dụng intthay vì lấy longgiá trị của FileInfođộ dài và chuyển đổi nó.
vapcguy

9

Tùy thuộc vào tần suất hoạt động, kích thước của tệp và số lượng tệp bạn đang xem, có các vấn đề về hiệu suất khác cần xem xét. Một điều cần nhớ, là mỗi mảng byte của bạn sẽ được phát hành trong sự thương xót của trình thu gom rác. Nếu bạn không lưu trữ bất kỳ dữ liệu nào trong số đó, cuối cùng bạn có thể tạo ra rất nhiều rác và sẽ mất phần lớn hiệu suất của mình xuống % Time trong GC. Nếu các khối lớn hơn 85K, bạn sẽ phân bổ cho Heap đối tượng lớn (LOH) sẽ yêu cầu một bộ sưu tập của tất cả các thế hệ để giải phóng (điều này rất tốn kém và trên máy chủ sẽ dừng tất cả thực thi trong khi nó đang diễn ra ). Ngoài ra, nếu bạn có rất nhiều đối tượng trên LOH, bạn có thể bị phân mảnh LOH (LOH không bao giờ được nén) dẫn đến hiệu suất kém và ngoại lệ bộ nhớ. Bạn có thể tái chế quy trình một khi bạn đạt đến một điểm nhất định, nhưng tôi không biết liệu đó có phải là cách thực hành tốt nhất không.

Vấn đề là, bạn nên xem xét toàn bộ vòng đời của ứng dụng trước khi chỉ cần đọc tất cả các byte vào bộ nhớ theo cách nhanh nhất có thể hoặc bạn có thể giao dịch hiệu suất ngắn hạn để có hiệu suất tổng thể.


mã nguồn C # về nó, cho quản lý garbage collector, chunks, hiệu suất, quầy kiện , ...
PreguntonCojoneroCabrón

6

Tôi muốn nói BinaryReaderlà ổn, nhưng có thể được tái cấu trúc cho điều này, thay vì tất cả các dòng mã để lấy độ dài của bộ đệm:

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

Nên sử dụng tốt hơn .ReadAllBytes(), vì tôi đã thấy trong các nhận xét về phản hồi hàng đầu bao gồm .ReadAllBytes()một trong những người bình luận có vấn đề với các tệp> 600 MB, vì BinaryReaderý nghĩa của việc này là như vậy. Ngoài ra, đưa nó vào một usingtuyên bố đảm bảo FileStreamBinaryReaderđược đóng và xử lý.


Đối với C #, cần sử dụng "bằng cách sử dụng (FileStream fs = File.OpenRead (fileName))" thay vì "bằng cách sử dụng (FileStream fs = new File.OpenRead (fileName))" như đã nêu ở trên. Chỉ cần xóa từ khóa mới trước File.OpenRead ()
Syed Mohamed

@Syed Đoạn mã trên WAS được viết cho C #, nhưng bạn đúng newlà không cần thiết ở đó. Đã xóa.
vapcguy

1

Trong trường hợp với 'một tệp lớn' có nghĩa là vượt quá giới hạn 4GB, thì logic mã bằng văn bản sau đây của tôi là phù hợp. Vấn đề chính cần chú ý là kiểu dữ liệu LONG được sử dụng với phương thức XEM. Là một LONG có thể chỉ ra ngoài 2 ^ 32 ranh giới dữ liệu. Trong ví dụ này, mã đang xử lý trước tiên xử lý tệp lớn trong các khối 1GB, sau khi toàn bộ khối 1GB lớn được xử lý, các byte còn lại (<1GB) được xử lý. Tôi sử dụng mã này với tính toán CRC của các tệp vượt quá kích thước 4GB. (sử dụng https://crc32c.machinezoo.com/ để tính toán crc32c trong ví dụ này)

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

Sử dụng lớp BufferedStream trong C # để cải thiện hiệu suất. Bộ đệm là một khối byte trong bộ nhớ được sử dụng để lưu trữ dữ liệu, do đó giảm số lượng cuộc gọi đến hệ điều hành. Bộ đệm cải thiện hiệu suất đọc và viết.

Xem phần sau đây để biết ví dụ về mã và giải thích bổ sung: http://msdn.microsoft.com/en-us/l Library / system.io.bufferedstream.aspx


Điểm của việc sử dụng BufferedStreamkhi bạn đọc toàn bộ cùng một lúc là gì?
Mehrdad Afshari

Ông yêu cầu hiệu suất tốt nhất để không đọc các tập tin cùng một lúc.
Todd Moses

9
Hiệu suất có thể đo lường được trong bối cảnh của một hoạt động. Bộ đệm bổ sung cho một luồng mà bạn đang đọc tuần tự, tất cả cùng một lúc, vào bộ nhớ không có khả năng được hưởng lợi từ bộ đệm bổ sung.
Mehrdad Afshari

0

dùng cái này:

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

2
Chào mừng bạn đến với Stack Overflow! Vì giải thích là một phần quan trọng của câu trả lời trên nền tảng này, vui lòng giải thích mã của bạn và cách giải quyết vấn đề trong câu hỏi và tại sao nó có thể tốt hơn các câu trả lời khác. Hướng dẫn của chúng tôi Làm thế nào để viết một câu trả lời tốt có thể hữu ích cho bạn. Cảm ơn
David

0

Tổng quan: nếu hình ảnh của bạn được thêm dưới dạng hành động = tài nguyên được nhúng thì hãy sử dụng GetExecutingAssugging để truy xuất tài nguyên jpg vào luồng sau đó đọc dữ liệu nhị phân trong luồng vào một mảng byte

   public byte[] GetAImage()
    {
        byte[] bytes=null;
        var assembly = Assembly.GetExecutingAssembly();
        var resourceName = "MYWebApi.Images.X_my_image.jpg";

        using (Stream stream = assembly.GetManifestResourceStream(resourceName))
        {
            bytes = new byte[stream.Length];
            stream.Read(bytes, 0, (int)stream.Length);
        }
        return bytes;

    }

-4

Tôi khuyên bạn nên thử Response.TransferFile()phương pháp sau đó Response.Flush()Response.End()để phục vụ các tệp lớn của bạn.


-7

Nếu bạn đang xử lý các tệp trên 2 GB, bạn sẽ thấy rằng các phương pháp trên không thành công.

Việc chuyển luồng sang MD5 sẽ dễ dàng hơn nhiều và cho phép điều đó giúp phân chia tệp của bạn cho bạn:

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
Tôi không thấy cách mã có liên quan đến câu hỏi (hoặc những gì bạn đề xuất trong văn bản bằng văn bản)
Vojtech B
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.