การสร้างอาร์เรย์ไบต์จากสตรีม


913

วิธีการที่ต้องการสำหรับการสร้างอาร์เรย์ไบต์จากอินพุตสตรีมคืออะไร?

นี่คือโซลูชันปัจจุบันของฉันกับ. NET 3.5

Stream s;
byte[] b;

using (BinaryReader br = new BinaryReader(s))
{
    b = br.ReadBytes((int)s.Length);
}

มันเป็นความคิดที่ดีกว่าถ้าจะอ่านและเขียนชิ้นของกระแสหรือไม่


60
แน่นอนอีกคำถามคือคุณควรสร้างไบต์ [] จากสตรีม ... สำหรับข้อมูลขนาดใหญ่มันจะดีกว่าที่จะรักษากระแสเป็นดีกระแส!
Marc Gravell

2
แน่นอนคุณควรใช้สตรีมแทนไบต์ [] แต่มี API ของระบบบางตัวที่ไม่รองรับสตรีม ตัวอย่างเช่นคุณไม่สามารถสร้างX509Certificate2จากกระแสคุณต้องให้ไบต์ [] (หรือสตริง) ในกรณีนี้ก็ปรับตั้งแต่ใบรับรอง x509 อาจจะไม่ได้เป็นข้อมูลขนาดใหญ่
0xced

คำตอบ:


1294

s.Lengthจริงๆมันขึ้นอยู่หรือไม่ว่าคุณสามารถไว้วางใจ สำหรับสตรีมจำนวนมากคุณไม่ทราบว่าจะมีข้อมูลมากแค่ไหน ในกรณีเช่นนี้ - และก่อน. NET 4 - ฉันต้องการใช้รหัสเช่นนี้:

public static byte[] ReadFully(Stream input)
{
    byte[] buffer = new byte[16*1024];
    using (MemoryStream ms = new MemoryStream())
    {
        int read;
        while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
        {
            ms.Write(buffer, 0, read);
        }
        return ms.ToArray();
    }
}

ด้วย. NET 4 ขึ้นไปฉันจะใช้Stream.CopyToซึ่งโดยทั่วไปจะเทียบเท่ากับลูปในรหัสของฉัน - สร้างการMemoryStreamโทรstream.CopyTo(ms)แล้วกลับมาms.ToArray()และจากนั้นกลับมางานเสร็จแล้ว

ฉันควรอธิบายว่าทำไมคำตอบของฉันจึงยาวกว่าคำตอบอื่น Stream.Readไม่รับประกันว่ามันจะอ่านทุกอย่างที่ถาม หากคุณกำลังอ่านจากสตรีมเครือข่ายมันอาจอ่านค่าแพ็คเก็ตหนึ่งแล้วส่งคืนแม้ว่าจะมีข้อมูลเพิ่มเติมในไม่ช้าBinaryReader.Readจะดำเนินต่อไปจนกว่าจะสิ้นสุดของสตรีมหรือขนาดที่คุณระบุ แต่คุณยังต้องรู้ขนาดที่จะเริ่มต้นด้วย

วิธีการข้างต้นจะทำการอ่าน (และคัดลอกไปยัง a MemoryStream) จนกว่าจะหมดข้อมูล จากนั้นจะขอMemoryStreamให้ส่งคืนสำเนาของข้อมูลในอาร์เรย์ หากคุณรู้ว่าขนาดเริ่มต้นด้วย - หรือคิดว่าคุณรู้ขนาดโดยไม่แน่ใจ - คุณสามารถสร้างMemoryStreamขนาดให้เริ่มต้นด้วย ในทำนองเดียวกันคุณสามารถตรวจสอบในตอนท้ายและถ้าความยาวของกระแสมีขนาดเดียวกันกับบัฟเฟอร์ (คืนโดยMemoryStream.GetBuffer) จากนั้นคุณก็สามารถกลับบัฟเฟอร์ ดังนั้นรหัสข้างต้นจึงไม่ได้รับการปรับให้เหมาะสม แต่อย่างน้อยจะต้องถูกต้อง ไม่รับผิดชอบใด ๆ สำหรับการปิดสตรีมผู้เรียกควรทำเช่นนั้น

ดูบทความนี้สำหรับข้อมูลเพิ่มเติม (และการใช้งานทางเลือก)


9
@ จอนก็อาจจะมีมูลค่าการกล่าวขวัญyoda.arachsys.com/csharp/readbinary.html
แซม Saffron

6
@Jeff: เราไม่มีบริบทที่นี่จริง ๆ แต่ถ้าคุณเขียนถึงกระแสข้อมูลใช่แล้วคุณต้อง "ย้อนกลับ" ก่อนอ่าน มี "เคอร์เซอร์" เพียงอันเดียวที่บอกว่าคุณอยู่ที่ไหนในกระแสไม่ใช่สำหรับอ่านและอีกอันสำหรับเขียน
Jon Skeet

5
@Jeff: มันเป็นความรับผิดชอบของผู้โทร ท้ายที่สุดสตรีมอาจไม่สามารถค้นหาได้ (เช่นสตรีมเครือข่าย) หรืออาจไม่จำเป็นต้องย้อนกลับ
Jon Skeet

18
ฉันถามทำไม16*1024โดยเฉพาะ
Anyname Donotcare

5
@just_name: ฉันไม่รู้ว่าสิ่งนี้มีความสำคัญใด ๆ แต่ (16 * 1024) เป็นครึ่งหนึ่งของ Int16.MaxValue :)
caesay

734

CopyToในขณะที่คำตอบของจอนถูกต้องเขาจะเขียนใหม่รหัสที่มีอยู่แล้วใน ดังนั้นสำหรับ. Net 4 ใช้โซลูชันของ Sandip แต่สำหรับ. Net เวอร์ชันก่อนหน้าใช้คำตอบของ Jon รหัสของ Sandip จะได้รับการปรับปรุงโดยการใช้ "การใช้" เป็นข้อยกเว้นCopyToในหลาย ๆ สถานการณ์มีโอกาสมากและจะMemoryStreamไม่ทิ้ง

public static byte[] ReadFully(Stream input)
{
    using (MemoryStream ms = new MemoryStream())
    {
        input.CopyTo(ms);
        return ms.ToArray();
    }
}

6
มันแตกต่างกันอย่างไรระหว่างคำตอบของคุณกับของจอน? นอกจากนี้ฉันต้องทำอินพุตนี้ตำแหน่ง = 0 เพื่อให้ CopyTo ทำงาน
Jeff

1
@ นาธานอ่านไฟล์จากเว็บไคลเอ็นต์ (filizesize = 1mb) - iis จะต้องโหลดทั้ง 1mb ไปยังหน่วยความจำใช่มั้ย
Royi Namir

5
@ เจฟฟ์คำตอบของฉันจะใช้ได้เฉพาะกับ. Net 4 หรือสูงกว่า Jons จะทำงานในเวอร์ชันที่ต่ำกว่าโดยการเขียนฟังก์ชันการทำงานที่ให้ไว้กับเราในรุ่นที่ใหม่กว่า คุณถูกต้องที่ CopyTo จะคัดลอกจากตำแหน่งปัจจุบันเท่านั้นหากคุณมีสตรีม Seekable และคุณต้องการคัดลอกตั้งแต่ต้นคุณสามารถย้ายไปที่จุดเริ่มต้นโดยใช้รหัสหรืออินพุตของคุณค้นหา (0, SeekOrigin.Begin) แม้ว่าในหลายกรณีสตรีมของคุณอาจไม่สามารถมองเห็นได้
Nathan Phillips

5
มันอาจคุ้มค่าที่จะตรวจสอบว่าinputมีอยู่แล้วMemorySteamและลัดวงจร ฉันรู้ว่ามันจะโง่ของผู้โทรผ่านMemoryStreamแต่ ...
Jodrell

3
@Jodrell แน่นอนดังนั้น หากคุณกำลังคัดลอกสตรีมเล็ก ๆ นับล้านลงในหน่วยความจำและหนึ่งในนั้นคือMemoryStreamการเพิ่มประสิทธิภาพที่เหมาะสมในบริบทของคุณหรือไม่คือการเปรียบเทียบเวลาที่ใช้ในการทำ Conversion หลายล้านครั้งเทียบกับเวลาที่ใช้เพื่อคัดลอกรายการที่MemoryStreamเข้าสู่ MemoryStreamอื่น
Nathan Phillips

114

แค่อยากจะชี้ให้เห็นว่าในกรณีที่คุณมี MemoryStream ที่คุณมีอยู่แล้วmemorystream.ToArray()ว่า

นอกจากนี้หากคุณกำลังติดต่อกับสตรีมของชนิดย่อยที่ไม่รู้จักหรือแตกต่างกันและคุณสามารถรับได้MemoryStreamคุณสามารถถ่ายทอดวิธีการดังกล่าวสำหรับกรณีเหล่านั้นและยังคงใช้คำตอบที่ยอมรับสำหรับผู้อื่นเช่นนี้

public static byte[] StreamToByteArray(Stream stream)
{
    if (stream is MemoryStream)
    {
        return ((MemoryStream)stream).ToArray();                
    }
    else
    {
        // Jon Skeet's accepted answer 
        return ReadFully(stream);
    }
}

1
อืมสิ่งที่ upvotes ทั้งหมดมีไว้เพื่ออะไร? แม้จะมีสมมติฐานที่ดีที่สุด แต่ก็ใช้ได้กับสตรีมที่มีอยู่แล้วMemoryStreamเท่านั้น แน่นอนว่าตัวอย่างยังไม่สมบูรณ์อย่างเห็นได้ชัดว่ามันใช้ตัวแปรที่ไม่กำหนดค่าเริ่มต้นอย่างไร
Roman Starkov

3
ถูกต้องขอบคุณที่ชี้ให้เห็น จุดยังคงยืนสำหรับ MemoryStream แม้ว่าดังนั้นฉันคงที่เพื่อสะท้อนที่
Fernando Neira

เพียงแค่พูดถึงว่าสำหรับ MemoryStream ความเป็นไปได้อีกอย่างก็คือ MemoryStream.GetBuffer () แม้ว่าจะมี gotchas บางส่วนที่เกี่ยวข้อง ดูstackoverflow.com/questions/1646193/ …และkrishnabhargav.blogspot.dk/2009/06/…
RenniePet

4
นี่เป็นการแนะนำบั๊กในโค้ดของ Skeet หากคุณโทรstream.Seek(1L, SeekOrigin.Begin)ก่อนที่คุณจะเรียกใช้อย่างพร้อมเพรียงหากสตรีมเป็นสตรีมหน่วยความจำคุณจะได้รับ 1 ไบต์มากกว่าสตรีมอื่น ๆ หากผู้โทรคาดว่าจะอ่านจากตำแหน่งปัจจุบันถึงจุดสิ้นสุดของสตรีมคุณต้องไม่ใช้CopyToหรือToArray(); ในกรณีส่วนใหญ่สิ่งนี้จะไม่เป็นปัญหา แต่ถ้าผู้โทรไม่ทราบเกี่ยวกับพฤติกรรมที่แปลกประหลาดพวกเขาจะสับสน
แจ้ง

67
MemoryStream ms = new MemoryStream();
file.PostedFile.InputStream.CopyTo(ms);
var byts = ms.ToArray();
ms.Dispose();

9
ควรสร้าง MemoryStream ด้วย "new MemoryStream (file.PostedFile.ContentLength)" เพื่อหลีกเลี่ยงการกระจายตัวของหน่วยความจำ
แดนแรนดอล์ฟ

52

แค่คู่สามีภรรยาของฉัน ... การฝึกฝนที่ฉันมักจะใช้คือการจัดระเบียบวิธีการเช่นนี้ในฐานะผู้ช่วยที่กำหนดเอง

public static class StreamHelpers
{
    public static byte[] ReadFully(this Stream input)
    {
        using (MemoryStream ms = new MemoryStream())
        {
            input.CopyTo(ms);
            return ms.ToArray();
        }
    }
}

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


5
โปรดทราบว่าสิ่งนี้จะไม่ทำงานใน. NET 3.5 และต่ำกว่าเนื่องจากCopyToไม่สามารถใช้ได้Streamจนถึง 4.0
ทิม

16

คุณสามารถใช้วิธี ToArray () ของคลาส MemoryStream เช่น

MemoryStream ms = (MemoryStream)dataInStream;
byte[] imageBytes = ms.ToArray();

10

คุณสามารถทำให้มันยิ่งกว่าเดิมด้วยส่วนขยาย:

namespace Foo
{
    public static class Extensions
    {
        public static byte[] ToByteArray(this Stream stream)
        {
            using (stream)
            {
                using (MemoryStream memStream = new MemoryStream())
                {
                     stream.CopyTo(memStream);
                     return memStream.ToArray();
                }
            }
        }
    }
}

แล้วเรียกมันว่าเป็นวิธีการปกติ:

byte[] arr = someStream.ToByteArray()

67
ฉันคิดว่าเป็นความคิดที่ดีที่จะใส่สตรีมอินพุตในบล็อกที่ใช้ ความรับผิดชอบนั้นควรอยู่ในขั้นตอนการโทร
Jeff

7

ฉันพบข้อผิดพลาดในการรวบรวมเวลาด้วยรหัสของ Bob (เช่นผู้ถาม) Stream.Length นั้นยาวในขณะที่ BinaryReader.ReadBytes ใช้พารามิเตอร์จำนวนเต็ม ในกรณีของฉันฉันไม่ได้คาดหวังว่าจะจัดการกับสตรีมขนาดใหญ่พอที่จะต้องการความแม่นยำที่ยาวนานดังนั้นฉันจึงใช้สิ่งต่อไปนี้:

Stream s;
byte[] b;

if (s.Length > int.MaxValue) {
  throw new Exception("This stream is larger than the conversion algorithm can currently handle.");
}

using (var br = new BinaryReader(s)) {
  b = br.ReadBytes((int)s.Length);
}

5

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

public static class StreamHelpers
{
    public static byte[] ReadFully(this Stream input)
    {
        var ms = new MemoryStream();
        input.CopyTo(ms);
        return ms.ToArray();
    }
}

3

รายการด้านบนก็โอเค ... แต่คุณจะพบข้อมูลเสียหายเมื่อคุณส่งข้อมูลผ่าน SMTP (ถ้าคุณต้องการ) ฉันได้เปลี่ยนเป็นอย่างอื่นที่จะช่วยส่งไบต์อย่างถูกต้องสำหรับไบต์: '

using System;
using System.IO;

        private static byte[] ReadFully(string input)
        {
            FileStream sourceFile = new FileStream(input, FileMode.Open); //Open streamer
            BinaryReader binReader = new BinaryReader(sourceFile);
            byte[] output = new byte[sourceFile.Length]; //create byte array of size file
            for (long i = 0; i < sourceFile.Length; i++)
                output[i] = binReader.ReadByte(); //read until done
            sourceFile.Close(); //dispose streamer
            binReader.Close(); //dispose reader
            return output;
        }'

ฉันไม่เห็นว่ารหัสนี้จะหลีกเลี่ยงความเสียหายของข้อมูลหรือไม่ คุณอธิบายได้ไหม
Nippey

สมมติว่าคุณมีรูปภาพและคุณต้องการส่งผ่าน SMTP คุณอาจใช้การเข้ารหัส base64 ด้วยเหตุผลบางอย่างไฟล์จะเสียหายหากคุณแยกไฟล์เป็นไบต์ อย่างไรก็ตามการใช้ตัวอ่านไบนารีจะอนุญาตให้ส่งไฟล์ได้สำเร็จ
NothinRandom

3
ค่อนข้างเก่า แต่ฉันรู้สึกว่าหมีตัวนี้พูดถึง - การใช้ @NothinRandom ให้ผลงานกับสตริงไม่ใช่ลำธาร มันอาจจะง่ายที่สุดในการใช้ File.ReadAllBytes ในกรณีนี้
XwipeoutX

1
Downvote เนื่องจากรูปแบบรหัสอันตราย (ไม่มีการกำจัด / ใช้งานโดยอัตโนมัติ)
arni

น่าเสียดายเพียง -1 ที่ได้รับอนุญาตไม่มีอะไรเกี่ยวข้องกับคำถามพารามิเตอร์ชื่อไฟล์ชื่ออินพุตไม่ทิ้งไม่มีบัฟเฟอร์การอ่านไม่มีโหมดไฟล์และตัวอ่านไบนารีเพื่ออ่านไบต์ต่อไบต์เพราะเหตุใด
Aridane Álamo

2

สร้างคลาสผู้ช่วยและอ้างอิงได้ทุกที่ที่คุณต้องการใช้

public static class StreamHelpers
{
    public static byte[] ReadFully(this Stream input)
    {
        using (MemoryStream ms = new MemoryStream())
        {
            input.CopyTo(ms);
            return ms.ToArray();
        }
    }
}

2

ในเนมสเปซ RestSharp.Extensions มีเมธอด ReadAsBytes ภายในเมธอดนี้ใช้ MemoryStream และมีรหัสเหมือนกันในบางตัวอย่างในหน้านี้ แต่เมื่อคุณใช้ RestSharp นี่เป็นวิธีที่ง่ายที่สุด

using RestSharp.Extensions;
var byteArray = inputStream.ReadAsBytes();

1

คุณสามารถใช้วิธีการขยายนี้

public static class StreamExtensions
{
    public static byte[] ToByteArray(this Stream stream)
    {
        var bytes = new List<byte>();

        int b;
        while ((b = stream.ReadByte()) != -1)
            bytes.Add((byte)b);

        return bytes.ToArray();
    }
}

1

นี่คือฟังก์ชั่นที่ฉันใช้ทดสอบและทำงานได้ดี โปรดจำไว้ว่า 'การป้อนข้อมูล' ไม่ควรเป็นโมฆะและ 'input.position' ควรรีเซ็ตเป็น '0' ก่อนอ่านมิฉะนั้นจะทำให้การอ่านวนซ้ำและจะไม่มีการแปลงเป็นอาร์เรย์

    public static byte[] StreamToByteArray(Stream input)
    {
        if (input == null)
            return null;
        byte[] buffer = new byte[16 * 1024];
        input.Position = 0;
        using (MemoryStream ms = new MemoryStream())
        {
            int read;
            while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
            {
                ms.Write(buffer, 0, read);
            }
            byte[] temp = ms.ToArray();

            return temp;
        }
    }

-1
public static byte[] ToByteArray(Stream stream)
    {
        if (stream is MemoryStream)
        {
            return ((MemoryStream)stream).ToArray();
        }
        else
        {
            byte[] buffer = new byte[16 * 1024];
            using (MemoryStream ms = new MemoryStream())
            {
                int read;
                while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    ms.Write(buffer, 0, read);
                }
                return ms.ToArray();
            }
        }            
    }

คุณเพิ่งคัดลอกรหัสจากคำตอบ # 1 และ # 3 โดยไม่ต้องเพิ่มสิ่งใดที่มีค่า โปรดอย่าทำอย่างนั้น :)
CodeCaster

เมื่อคุณเพิ่มรหัสให้อธิบายวิธีการแก้ปัญหาที่เสนอของคุณในไม่ช้า
yakobom

-5

ฉันสามารถทำให้มันทำงานบนบรรทัดเดียว:

byte [] byteArr= ((MemoryStream)localStream).ToArray();

ตามที่ชี้แจงโดยjohnnyRoseโค้ดด้านบนจะใช้ได้กับ MemoryStream เท่านั้น


2
เกิดอะไรขึ้นถ้าlocalStreamไม่ได้เป็นMemoryStream? รหัสนี้จะล้มเหลว
johnnyRose

localStream ต้องเป็นวัตถุที่ใช้สตรีม เพิ่มเติมเกี่ยวกับกระแสวัตถุตามที่นี่stackoverflow.com/questions/8156896/...
Abba

1
สิ่งที่ผมพยายามที่จะแนะนำคือถ้าคุณพยายามที่จะโยนlocalStreamไปMemoryStreamแต่localStreamเป็นไม่ได้MemoryStreamก็จะล้มเหลว รหัสนี้จะรวบรวมดี localStreamแต่ก็อาจล้มเหลวที่รันไทม์ขึ้นอยู่กับชนิดที่แท้จริงของ คุณไม่สามารถสร้างประเภทพื้นฐานไปเป็นประเภทย่อยได้โดยพลการ อ่านเพิ่มเติมที่นี่ นี่เป็นอีกตัวอย่างที่ดีที่อธิบายว่าทำไมคุณไม่สามารถทำสิ่งนี้ได้ตลอดเวลา
johnnyRose

ในการอธิบายอย่างละเอียดเกี่ยวกับความคิดเห็นข้างต้นของฉัน: MemoryStreams ทั้งหมดเป็น Streams แต่ไม่ใช่ Streams ทั้งหมดเป็น MemoryStreams
johnnyRose

วัตถุที่ใช้ Stream ทั้งหมดมี Stream เป็นชนิดพื้นฐาน และสตรีมเองสามารถแปลงเป็นสตรีมหน่วยความจำได้เสมอ ไม่ว่าคุณจะพยายามโยนไปที่ Meomry Stream แล้วก็ตาม เป้าหมายของเราที่นี่คือการแปลงวัตถุกระแสเป็นอาร์เรย์ไบต์ คุณสามารถให้กรณี signle ที่มันจะล้มเหลว?
Abba
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.