Tạo một mảng byte từ một luồng


913

Phương thức ưa thích để tạo một mảng byte từ luồng đầu vào là gì?

Đây là giải pháp hiện tại của tôi với .NET 3.5.

Stream s;
byte[] b;

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

Nó vẫn là một ý tưởng tốt hơn để đọc và viết các đoạn của luồng?


60
Tất nhiên, một câu hỏi khác là bạn có nên tạo một byte [] từ một luồng ... đối với dữ liệu lớn, tốt hơn là nên xử lý luồng đó, cũng như một luồng!
Marc Gravell

2
Thật vậy, có lẽ bạn nên sử dụng một luồng thay vì một byte []. Nhưng có một số API hệ thống không hỗ trợ luồng. Ví dụ: bạn không thể tạo X509Certert2 từ một luồng, bạn phải cung cấp cho nó một byte [] (hoặc một chuỗi). Trong trường hợp này, nó ổn vì chứng chỉ x509 có thể không phải là dữ liệu lớn .
0xced

Câu trả lời:


1294

Nó thực sự phụ thuộc vào việc bạn có thể tin tưởng hay không s.Length. Đối với nhiều luồng, bạn không biết sẽ có bao nhiêu dữ liệu. Trong những trường hợp như vậy - và trước .NET 4 - Tôi sẽ sử dụng mã như thế này:

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

Với .NET 4 trở lên, tôi sẽ sử dụng Stream.CopyTo, về cơ bản tương đương với vòng lặp trong mã của tôi - tạo MemoryStream, gọi stream.CopyTo(ms)và sau đó trả về ms.ToArray(). Công việc hoàn thành.

Tôi có lẽ nên giải thích tại sao câu trả lời của tôi dài hơn những người khác. Stream.Readkhông đảm bảo rằng nó sẽ đọc mọi thứ nó yêu cầu. Ví dụ: nếu bạn đang đọc từ một luồng mạng, nó có thể đọc giá trị của một gói và sau đó quay lại, ngay cả khi sẽ có nhiều dữ liệu sớm hơn. BinaryReader.Readsẽ tiếp tục cho đến khi kết thúc luồng hoặc kích thước được chỉ định của bạn, nhưng bạn vẫn phải biết kích thước để bắt đầu.

Phương pháp trên sẽ tiếp tục đọc (và sao chép vào a MemoryStream) cho đến khi hết dữ liệu. Sau đó nó yêu cầu MemoryStreamtrả về một bản sao của dữ liệu trong một mảng. Nếu bạn biết kích thước để bắt đầu - hoặc nghĩ rằng bạn biết kích thước, mà không chắc chắn - bạn có thể xây dựng MemoryStreamkích thước đó để bắt đầu. Tương tự như vậy, bạn có thể đặt một kiểm tra ở cuối và nếu độ dài của luồng có cùng kích thước với bộ đệm (được trả về bởi MemoryStream.GetBuffer) thì bạn có thể trả về bộ đệm. Vì vậy, đoạn mã trên không được tối ưu hóa hoàn toàn, nhưng ít nhất sẽ đúng. Nó không chịu bất kỳ trách nhiệm nào trong việc đóng luồng - người gọi nên làm điều đó.

Xem bài viết này để biết thêm thông tin (và thực hiện thay thế).


9
@Jon, có thể đáng nói đến yoda.arachsys.com/csharp/readbinary.html
Sam Saffron

6
@Jeff: Chúng tôi không thực sự có bối cảnh ở đây, nhưng nếu bạn đã viết lên một luồng, thì có, bạn cần phải "tua lại" nó trước khi đọc. Chỉ có một "con trỏ" cho biết bạn đang ở đâu trong luồng - không phải là một con để đọc và một con trỏ riêng để viết.
Jon Skeet

5
@Jeff: Đó là trách nhiệm của người gọi. Rốt cuộc, luồng có thể không thể tìm kiếm (ví dụ: luồng mạng) hoặc đơn giản là không cần phải tua lại.
Jon Skeet

18
Tôi có thể hỏi tại sao 16*1024cụ thể?
Anyname Donotcare

5
@just_name: Tôi không biết điều này có ý nghĩa gì không, nhưng (16 * 1024) xảy ra là một nửa của Int16.MaxValue :)
caesay

735

Trong khi câu trả lời của Jon là chính xác, anh ta đang viết lại mã đã tồn tại CopyTo. Vì vậy, đối với .Net 4 sử dụng giải pháp của Sandip, nhưng đối với phiên bản trước của .Net, hãy sử dụng câu trả lời của Jon. Mã của Sandip sẽ được cải thiện bằng cách sử dụng "sử dụng" như các trường hợp ngoại lệ CopyTo, trong nhiều tình huống, rất có thể và sẽ MemoryStreamkhông được xử lý.

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

6
Nó khác nhau gì giữa câu trả lời của bạn và Jon? Ngoài ra, tôi phải thực hiện thao tác nhập liệu này.Pocation = 0 để CopyTo hoạt động.
Jeff

1
@nathan, đọc một tệp từ máy khách web (filizesize = 1mb) - iis sẽ phải tải toàn bộ 1mb vào bộ nhớ của nó phải không?
Royi Namir

5
@Jeff, câu trả lời của tôi sẽ chỉ hoạt động trên .Net 4 trở lên, Jons sẽ hoạt động trên các phiên bản thấp hơn bằng cách viết lại chức năng được cung cấp cho chúng tôi trong phiên bản sau. Bạn đã đúng rằng CopyTo sẽ chỉ sao chép từ vị trí hiện tại, nếu bạn có một luồng Tìm kiếm và bạn muốn sao chép từ đầu thì bạn có thể chuyển sang đầu bằng mã hoặc đầu vào của mình. (0, SeekOrigin.Begin), mặc dù trong nhiều trường hợp, luồng của bạn có thể không tìm được.
Nathan Phillips

5
nó có thể là giá trị kiểm tra nếu inputđã MemorySteamvà ngắn mạch. Tôi biết sẽ thật ngu ngốc khi người gọi vượt qua MemoryStreamnhưng ...
Jodrell 27/03/13

3
@Jodrell, Chính xác là như vậy. Nếu bạn sao chép hàng triệu luồng nhỏ vào bộ nhớ và một trong số đó là MemoryStreamviệc tối ưu hóa có hợp lý trong ngữ cảnh của bạn hay không là so sánh thời gian thực hiện hàng triệu chuyển đổi loại so với thời gian để sao chép một lần chuyển đổi MemoryStreamthành khác MemoryStream.
Nathan Phillips

114

Chỉ muốn chỉ ra rằng trong trường hợp bạn có MemoryStream, bạn đã có sẵn memorystream.ToArray()cho điều đó.

Ngoài ra, nếu bạn đang xử lý các luồng không xác định hoặc các kiểu con khác nhau và bạn có thể nhận được một MemoryStream, bạn có thể chuyển tiếp phương thức đã nói cho các trường hợp đó và vẫn sử dụng câu trả lời được chấp nhận cho các trường hợp khác, như sau:

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

1
Huh, tất cả các upvote để làm gì? Ngay cả với các giả định hào phóng nhất, điều này chỉ hoạt động đối với các luồng đã MemoryStreams. Tất nhiên, ví dụ này rõ ràng là không đầy đủ, về cách nó sử dụng một biến chưa được khởi tạo.
Roman Starkov

3
Điều đó đúng, cảm ơn vì đã chỉ ra điều đó. Điểm vẫn là viết tắt của MemoryStream, vì vậy tôi đã sửa nó để phản ánh điều đó.
Fernando Neira

Chỉ cần đề cập rằng đối với MemoryStream, một khả năng khác là MemoryStream.GetBuffer (), mặc dù có một số vấn đề liên quan. Xem stackoverflow.com/questions/1646193/ Lờikrishnabhargav.blogspot.dk/2009/06/ trộm
RenniePet

4
Điều này thực sự giới thiệu một lỗi vào mã của Skeet; Nếu bạn gọi stream.Seek(1L, SeekOrigin.Begin), trước khi bạn gọi một cách sẵn sàng, nếu luồng là luồng bộ nhớ, bạn sẽ nhận được thêm 1 byte so với nếu đó là bất kỳ luồng nào khác. Nếu người gọi dự kiến ​​sẽ đọc từ vị trí hiện tại đến cuối luồng thì bạn không được sử dụng CopyTohoặc ToArray(); Trong hầu hết các trường hợp, điều này sẽ không thành vấn đề, nhưng nếu người gọi không biết về hành vi kỳ quặc này thì họ sẽ bị nhầm lẫn.
leat

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

9
Nên tạo MemoryStream bằng "MemoryStream mới (file.PostedFile.ContentLpm)" để tránh phân mảnh bộ nhớ.
Dan Randolph

52

chỉ vài xu của tôi ... thực tế mà tôi thường sử dụng là tổ chức các phương thức như thế này như một người trợ giúp tùy chỉnh

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

thêm không gian tên vào tập tin cấu hình và sử dụng nó ở bất cứ đâu bạn muốn


5
Lưu ý rằng điều này sẽ không hoạt động trong .NET 3.5 trở xuống vì CopyTokhông có sẵn Streamcho đến 4.0.
Tim

16

Bạn chỉ có thể sử dụng phương thức ToArray () của lớp MemoryStream, ví dụ như

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

10

Bạn thậm chí có thể làm cho nó dễ dàng hơn với các tiện ích mở rộng:

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

Và sau đó gọi nó như một phương pháp thông thường:

byte[] arr = someStream.ToByteArray()

67
Tôi nghĩ rằng đó là một ý tưởng tồi để đặt luồng đầu vào trong một khối sử dụng. Trách nhiệm đó nên thuộc về thủ tục gọi.
Jeff

7

Tôi nhận được một lỗi thời gian biên dịch với mã của Bob (tức là người hỏi). Stream.Lipse dài trong khi BinaryReader.ReadBytes lấy tham số nguyên. Trong trường hợp của tôi, tôi không mong muốn xử lý Luồng đủ lớn để yêu cầu độ chính xác dài, vì vậy tôi sử dụng như sau:

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

Trong trường hợp bất cứ ai thích nó, đây là một giải pháp .NET 4+ duy nhất được hình thành như một phương thức mở rộng mà không cần cuộc gọi Vứt bỏ không cần thiết trên MemoryStream. Đây là một tối ưu hóa vô vọng tầm thường, nhưng đáng chú ý là việc không loại bỏ MemoryStream không phải là một thất bại thực sự.

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

3

Một ở trên là ok ... nhưng bạn sẽ gặp phải hỏng dữ liệu khi bạn gửi công cụ qua SMTP (nếu bạn cần). Tôi đã thay đổi thành thứ khác sẽ giúp gửi chính xác byte cho byte: '

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

Tôi không thấy nơi mã này tránh tham nhũng dữ liệu. Bạn có thể giải thích nó được không?
Nippey

Giả sử bạn có một bức ảnh và bạn muốn gửi nó qua SMTP. Bạn có thể sẽ sử dụng mã hóa base64. Vì một số lý do, tập tin bị hỏng nếu bạn chia nó thành byte. Tuy nhiên, sử dụng trình đọc nhị phân sẽ cho phép tệp được gửi thành công.
NothinRandom

3
Hơi cũ, nhưng tôi cảm thấy con gấu này đề cập đến - việc triển khai @NothinRandom cung cấp các tác phẩm với chuỗi chứ không phải luồng. Mặc dù vậy, có lẽ sẽ đơn giản nhất khi chỉ sử dụng File.ReadAllBytes trong trường hợp này.
XwipeoutX

1
Downvote vì kiểu mã nguy hiểm (không loại bỏ / sử dụng tự động).
arni

Đáng buồn là chỉ cho phép -1, không liên quan gì đến câu hỏi, tham số tên tệp có tên đầu vào, không xử lý, không có bộ đệm đọc, không có tên tệp và trình đọc nhị phân để đọc từng byte tại sao?
Aridane Álamo

2

Tạo một lớp trợ giúp và tham chiếu nó bất cứ nơi nào bạn muốn sử dụng nó.

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

2

Trong không gian tên RestSharp.Extensions có phương thức ReadAsBytes. Bên trong phương thức này được sử dụng MemoryStream và có cùng mã như trong một số ví dụ trên trang này nhưng khi bạn đang sử dụng RestSharp thì đây là cách dễ nhất.

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

1

Bạn có thể sử dụng phương pháp mở rộng này.

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

Đây là chức năng mà tôi đang sử dụng, đã thử nghiệm và hoạt động tốt. xin lưu ý rằng 'đầu vào' không nên rỗng và 'input.poseition' nên đặt lại thành '0' trước khi đọc nếu không nó sẽ phá vỡ vòng đọc và không có gì sẽ đọc để chuyển thành mảng.

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

Bạn chỉ cần sao chép mã từ câu trả lời số 1 và số 3 mà không cần thêm bất cứ thứ gì có giá trị. Xin đừng làm vậy. :)
CodeCaster

Khi bạn thêm một mã, cũng mô tả giải pháp đề xuất của bạn trong thời gian ngắn.
yakobom

-5

tôi đã có thể làm cho nó hoạt động trên một dòng duy nhất:

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

như được làm rõ bởi johnnyRose , mã trên sẽ chỉ hoạt động cho MemoryStream


2
Nếu localStreamkhông phải là một MemoryStream? Mã này sẽ thất bại.
johnnyRose 17/03/2017

localStream phải là một đối tượng dựa trên luồng. biết thêm về đối tượng dựa trên luồng tại đây stackoverflow.com/questions/8156896/
Abba

1
Những gì tôi đã cố gắng để gợi ý là, nếu bạn cố gắng cast localStreamđến một MemoryStream, nhưng localStreamkhông một MemoryStream, nó sẽ thất bại. Mã này sẽ biên dịch tốt, nhưng nó có thể thất bại khi chạy, tùy thuộc vào loại thực tế localStream. Bạn không thể luôn tự ý chuyển một loại cơ sở thành một loại con; đọc thêm ở đây . Đây là một ví dụ tốt khác giải thích lý do tại sao bạn không thể luôn luôn làm điều này.
johnnyRose 17/03/2017

Để giải thích về nhận xét trên của tôi: tất cả các MemoryStream đều là Luồng, nhưng không phải tất cả Luồng đều là MemoryStreams.
johnnyRose 17/03/2017

tất cả các đối tượng dựa trên Stream có Stream là loại cơ sở. Và chính Stream luôn có thể chuyển đổi thành luồng bộ nhớ. Bất kể đối tượng dựa trên luồng nào bạn cố gắng truyền tới Meomry Stream, nó sẽ luôn hoạt động. Mục tiêu của chúng tôi ở đây là chuyển đổi đối tượng luồng thành mảng byte. Bạn có thể cho tôi một trường hợp đăng nhập mà nó sẽ thất bại?
Abba
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.