วิธีที่ดีที่สุดในการอ่านไฟล์ขนาดใหญ่ลงในอาร์เรย์ไบต์ใน C #?


391

ฉันมีเว็บเซิร์ฟเวอร์ที่จะอ่านไฟล์ไบนารีขนาดใหญ่ (หลายเมกะไบต์) ลงในอาร์เรย์ไบต์ เซิร์ฟเวอร์อาจอ่านไฟล์หลายไฟล์ในเวลาเดียวกัน (คำขอหน้าเว็บที่แตกต่างกัน) ดังนั้นฉันกำลังมองหาวิธีที่เหมาะสมที่สุดในการทำเช่นนี้โดยไม่ต้องเสียภาษี CPU มากเกินไป รหัสด้านล่างดีพอหรือไม่

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
byte[] buff = File.ReadAllBytes(fileName)ตัวอย่างเช่นคุณสามารถย่อ
เจสซีซี. เครื่องตัด

3
ทำไมการเป็น webservice ของบุคคลที่สามจึงบอกเป็นนัยว่าไฟล์นั้นต้องอยู่ใน RAM อย่างสมบูรณ์ก่อนที่จะถูกส่งไปยัง webservice แทนที่จะสตรีม เว็บเซอร์จะไม่ทราบถึงความแตกต่าง
Brian

@Brian ลูกค้าบางคนไม่ทราบวิธีจัดการกับ. NET stream เช่น Java เป็นต้น เมื่อเป็นกรณีนี้สิ่งที่สามารถทำได้คือการอ่านไฟล์ทั้งหมดในอาร์เรย์ไบต์
sjeffrey

4
@sjeffrey: ฉันบอกว่าข้อมูลควรจะสตรีมไม่ผ่านเป็นสตรีม. NET ลูกค้าจะไม่ทราบความแตกต่างอย่างใดอย่างหนึ่ง
Brian

คำตอบ:


776

เพียงแทนที่สิ่งทั้งหมดด้วย:

return File.ReadAllBytes(fileName);

อย่างไรก็ตามหากคุณกังวลเกี่ยวกับปริมาณการใช้หน่วยความจำคุณไม่ควรอ่านไฟล์ทั้งหมดในหน่วยความจำพร้อมกัน คุณควรทำแบบนั้น


40
วิธีนี้ถูก จำกัด ไว้ที่ไฟล์ 2 ^ 32 ไบต์ (4.2 GB)
Mahmoud Farahat

11
File.ReadAllBytes พ่น OutOfMemoryException ด้วยไฟล์ขนาดใหญ่ (ทดสอบด้วยไฟล์ 630 MB และล้มเหลว)
sakito

6
@ juanjo.arana ใช่แล้ว ... แน่นอนว่าจะมีบางสิ่งที่ไม่เหมาะกับความทรงจำเสมอซึ่งในกรณีนี้ไม่มีคำตอบสำหรับคำถาม โดยทั่วไปคุณควรสตรีมไฟล์และไม่เก็บไว้ในหน่วยความจำทั้งหมด คุณอาจต้องการดูสิ่งนี้สำหรับการวัดstopgap
Mehrdad Afshari

4
มีข้อ จำกัด สำหรับขนาดอาร์เรย์ใน. NET แต่ใน. NET 4.5 คุณสามารถเปิดการสนับสนุนอาร์เรย์ขนาดใหญ่ (> 2GB) โดยใช้ตัวเลือกการกำหนดค่าพิเศษดูmsdn.microsoft.com/en-us/library/hh285054.aspx
ผิดกฎหมาย -immigrant

3
@harag ไม่และนั่นไม่ใช่คำถามที่ถาม
Mehrdad Afshari

72

ฉันอาจยืนยันว่าคำตอบที่นี่โดยทั่วไปคือ "ไม่" หากคุณไม่ต้องการข้อมูลทั้งหมดพร้อมกันให้ลองใช้Stream-based API (หรือตัวแปรตัวอ่าน / ตัววนซ้ำบางตัว) สิ่งนี้มีความสำคัญอย่างยิ่งเมื่อคุณมีการทำงานหลายขนาน (ตามที่แนะนำโดยคำถาม) เพื่อลดภาระของระบบและเพิ่มปริมาณงานให้มากที่สุด

ตัวอย่างเช่นหากคุณกำลังสตรีมข้อมูลไปยังผู้โทร:

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
หากต้องการเพิ่มในคำสั่งของคุณฉันขอแนะนำให้พิจารณาตัวจัดการ async ASP.NET หากคุณมีการดำเนินการที่ผูกไว้กับ I / O เช่นการสตรีมไฟล์ไปยังลูกค้า อย่างไรก็ตามหากคุณต้องอ่านไฟล์ทั้งหมดเพื่อ a byte[]ด้วยเหตุผลบางอย่างฉันขอแนะนำให้หลีกเลี่ยงการใช้สตรีมหรือสิ่งอื่นและเพียงแค่ใช้ระบบที่มีให้ API
Mehrdad Afshari

@ Mehrdad - เห็นด้วย; แต่บริบททั้งหมดยังไม่ชัดเจน MVC ในทำนองเดียวกันมีผลการกระทำสำหรับสิ่งนี้
Marc Gravell

ใช่ฉันต้องการข้อมูลทั้งหมดในครั้งเดียว กำลังจะไปยังเว็บเซอร์ของบุคคลที่สาม
Tony_Henrich

ระบบระบุ API คืออะไร
Tony_Henrich

1
@Tony: ฉันระบุไว้ในคำตอบของฉัน: File.ReadAllBytes.
Mehrdad Afshari

32

ฉันจะคิดอย่างนี้:

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

3
โปรดทราบว่าการดำเนินการนี้อาจหยุดชะงักเมื่อรับไฟล์ที่มีขนาดใหญ่มาก
vapcguy

28

รหัสของคุณสามารถเป็นปัจจัยในการนี้ (แทน 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;
} 

หมายเหตุ Integer.MaxValue - ข้อ จำกัด ขนาดไฟล์ที่อยู่โดยวิธีการอ่าน กล่าวอีกนัยหนึ่งคุณสามารถอ่านก้อน 2GB ได้ในครั้งเดียว

โปรดทราบว่าอาร์กิวเมนต์ล่าสุดของ FileStream คือขนาดบัฟเฟอร์

ฉันยังอยากจะขอแนะนำให้อ่านเกี่ยวกับFileStreamและBufferedStream

เช่นเคยโปรแกรมตัวอย่างง่ายๆในการทำโพรไฟล์ซึ่งเร็วที่สุดจะเป็นประโยชน์มากที่สุด

นอกจากนี้ฮาร์ดแวร์พื้นฐานของคุณจะมีผลอย่างมากต่อประสิทธิภาพ คุณใช้ฮาร์ดดิสก์ไดรฟ์บนเซิร์ฟเวอร์ที่มีแคชขนาดใหญ่และการ์ด RAID ที่มีแคชหน่วยความจำออนบอร์ดอยู่หรือไม่? หรือว่าคุณใช้ไดรฟ์มาตรฐานที่เชื่อมต่อกับพอร์ต IDE


ทำไมประเภทของฮาร์ดแวร์จึงสร้างความแตกต่าง ดังนั้นถ้าเป็น IDE คุณใช้วิธีการ. NET และถ้าเป็น RAID คุณใช้วิธีอื่น
Tony_Henrich

@Tony_Henrich - ไม่มีส่วนเกี่ยวข้องกับสิ่งที่คุณโทรออกจากภาษาการเขียนโปรแกรมของคุณ ฮาร์ดดิสก์มีหลายประเภท ตัวอย่างเช่นไดรฟ์ซีเกทถูกจัดประเภทเป็น "AS" หรือ "NS" โดย NS เป็นเซิร์ฟเวอร์ที่ใช้แคชแคชขนาดใหญ่โดยที่ไดรฟ์ "AS" นั้นเป็นไดรฟ์ที่ใช้คอมพิวเตอร์สำหรับผู้บริโภค ค้นหาความเร็วและอัตราการถ่ายโอนข้อมูลภายในมีผลต่อความเร็วที่คุณสามารถอ่านจากดิสก์ อาร์เรย์ RAID สามารถปรับปรุงประสิทธิภาพการอ่าน / เขียนผ่านการแคชได้อย่างมากมาย ดังนั้นคุณอาจจะสามารถอ่านไฟล์ทั้งหมดในครั้งเดียว แต่ฮาร์ดแวร์พื้นฐานยังคงเป็นปัจจัยในการตัดสินใจ

2
รหัสนี้มีข้อบกพร่องที่สำคัญ จำเป็นต้องอ่านเพื่อส่งคืนอย่างน้อย 1 ไบต์
mafu

ฉันจะตรวจสอบให้แน่ใจว่าได้ตัดส่วนที่ยาวออกไปของการแสดงพร้อมกับโครงสร้างที่เลือกแบบนี้: checked ((int) fs.Length)
tzup

ฉันจะทำvar binaryReader = new BinaryReader(fs); fileData = binaryReader.ReadBytes((int)fs.Length);ในusingคำสั่งนั้น แต่นั่นเป็นสิ่งที่มีประสิทธิภาพเหมือนกับที่ OP ทำฉันเพียงแค่ตัดโค๊ดของโค้ดออกมาโดยfs.Lengthทำการแคสท์intแทนการรับlongค่าของFileInfoความยาวและแปลงมัน
vapcguy

9

ขึ้นอยู่กับความถี่ของการทำงานขนาดของไฟล์และจำนวนไฟล์ที่คุณกำลังดูมีปัญหาเรื่องประสิทธิภาพอื่น ๆ ที่ต้องพิจารณา สิ่งหนึ่งที่ต้องจำคืออาร์เรย์ไบต์แต่ละอันของคุณจะถูกปล่อยออกมาด้วยความเมตตาของตัวเก็บขยะ หากคุณไม่ได้แคชข้อมูลใด ๆ คุณสามารถจบลงด้วยการสร้างขยะจำนวนมากและเสียประสิทธิภาพการทำงานส่วนใหญ่ไปเป็น% Time ใน GC. หาก chunks มีขนาดใหญ่กว่า 85K คุณจะได้รับการจัดสรรไปยัง Large Object Heap (LOH) ซึ่งจะต้องมีการรวบรวมคอลเล็กชั่นทุกรุ่นให้หมด (ซึ่งมีราคาแพงมากและบนเซิร์ฟเวอร์จะหยุดการประมวลผลทั้งหมดในขณะที่เกิด ) นอกจากนี้หากคุณมีวัตถุจำนวนมากบน LOH คุณสามารถจบลงด้วยการแยกส่วน LOH (LOH ไม่เคยถูกบีบอัด) ซึ่งนำไปสู่ประสิทธิภาพที่ไม่ดีและข้อยกเว้นหน่วยความจำไม่เพียงพอ คุณสามารถรีไซเคิลกระบวนการเมื่อคุณถึงจุดหนึ่ง แต่ฉันไม่ทราบว่าเป็นวิธีปฏิบัติที่ดีที่สุด

ประเด็นก็คือคุณควรพิจารณาวงจรชีวิตทั้งหมดของแอพก่อนที่จะต้องอ่านไบต์ทั้งหมดในหน่วยความจำให้เร็วที่สุดเท่าที่จะเป็นไปได้หรือคุณอาจซื้อขายแลกเปลี่ยนประสิทธิภาพระยะสั้นเพื่อประสิทธิภาพโดยรวม


แหล่งที่มาของรหัส C # เกี่ยวกับมันสำหรับการจัดการgarbage collector, chunks, ประสิทธิภาพ, เคาน์เตอร์เหตุการณ์ ...
PreguntonCojoneroCabrón

6

ฉันจะบอกว่าใช้ได้BinaryReaderแต่สามารถ refactored นี้แทนบรรทัดของรหัสเหล่านั้นสำหรับการรับความยาวของบัฟเฟอร์:

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

น่าจะดีกว่าการใช้.ReadAllBytes()งานเนื่องจากฉันเห็นในความคิดเห็นเกี่ยวกับการตอบกลับยอดนิยมที่มี.ReadAllBytes()ผู้วิจารณ์คนหนึ่งมีปัญหากับไฟล์> 600 MB เนื่องจาก a BinaryReaderมีความหมายสำหรับสิ่งนี้ นอกจากนี้การใส่ไว้ในusingคำสั่งทำให้มั่นใจได้FileStreamและBinaryReaderจะถูกปิดและกำจัด


สำหรับ C # ต้องใช้ "using (FileStream fs = File.OpenRead (fileName))" แทน "using" using (FileStream fs = new File.OpenRead (fileName)) "ตามที่ระบุไว้ด้านบน เพิ่งลบคำหลักใหม่ออกไปก่อน File.OpenRead ()
Syed Mohamed

@Syed รหัสด้านบน WAS เขียนขึ้นสำหรับ C # แต่คุณพูดถูกที่newไม่จำเป็นต้องมี ลบออก
vapcguy

1

ในกรณีที่มี 'ไฟล์ขนาดใหญ่' มีความหมายเกินขีด จำกัด 4GB แล้วตรรกะรหัสของฉันที่เขียนต่อไปนี้มีความเหมาะสม ปัญหาสำคัญที่ควรแจ้งให้ทราบคือชนิดข้อมูลแบบยาวที่ใช้กับวิธีการค้นหา LONG สามารถชี้ขอบเขตข้อมูลได้เกิน 2 ^ 32 ในตัวอย่างนี้รหัสกำลังประมวลผลครั้งแรกในการประมวลผลไฟล์ขนาดใหญ่ในหน่วยของ 1GB หลังจากประมวลผลชิ้นส่วนขนาด 1GB ทั้งหมดแล้วประมวลผลทางซ้ายที่เหลือ (<1GB) ไบต์ ฉันใช้รหัสนี้กับการคำนวณ CRC ของไฟล์เกินขนาด 4GB (ใช้https://crc32c.machinezoo.com/สำหรับการคำนวณ crc32c ในตัวอย่างนี้)

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

ใช้คลาส BufferedStream ใน C # เพื่อปรับปรุงประสิทธิภาพ บัฟเฟอร์คือบล็อกของไบต์ในหน่วยความจำที่ใช้ในการแคชข้อมูลซึ่งจะช่วยลดจำนวนการเรียกไปยังระบบปฏิบัติการ บัฟเฟอร์ปรับปรุงประสิทธิภาพการอ่านและเขียน

ดูตัวอย่างโค้ดและคำอธิบายเพิ่มเติมต่อไปนี้: http://msdn.microsoft.com/en-us/library/system.io.bufferedstream.aspx


การใช้ a BufferedStreamเมื่อคุณอ่านสิ่งทั้งหมดในครั้งเดียวคืออะไร?
Mehrdad Afshari

เขาขอประสิทธิภาพที่ดีที่สุดไม่ให้อ่านไฟล์ในครั้งเดียว
โทดด์โมเสส

9
ประสิทธิภาพสามารถวัดได้ในบริบทของการดำเนินการ การบัฟเฟอร์เพิ่มเติมสำหรับสตรีมที่คุณอ่านตามลำดับทั้งหมดในครั้งเดียวไปยังหน่วยความจำไม่น่าจะได้รับประโยชน์จากบัฟเฟอร์พิเศษ
Mehrdad Afshari

0

ใช้สิ่งนี้:

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

2
ยินดีต้อนรับสู่ Stack Overflow! เนื่องจากคำอธิบายเป็นส่วนสำคัญของคำตอบในแพลตฟอร์มนี้โปรดอธิบายรหัสของคุณและวิธีแก้ปัญหาของคำถามและสาเหตุที่อาจดีกว่าคำตอบอื่น ๆ คำแนะนำของเราวิธีการเขียนคำตอบที่ดีอาจเป็นประโยชน์สำหรับคุณ ขอบคุณ
เดวิด

-4

ฉันขอแนะนำให้ลองใช้Response.TransferFile()วิธีนี้Response.Flush()และResponse.End()เพื่อให้บริการไฟล์ขนาดใหญ่ของคุณ


-7

หากคุณจัดการกับไฟล์ที่มีขนาดสูงกว่า 2 GB คุณจะพบว่าวิธีการด้านบนล้มเหลว

ง่ายกว่ามากเพียงแค่ส่งสตรีมไปที่MD5และอนุญาตให้ไฟล์เหล่านี้ของคุณ:

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
ฉันไม่เห็นรหัสที่เกี่ยวข้องกับคำถาม (หรือสิ่งที่คุณแนะนำในข้อความที่เป็นลายลักษณ์อักษร)
Vojtech B
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.