C#使用流(streams)

135

流对我来说有点神秘。我不知道该使用哪个流以及如何使用它们。可以有人解释一下如何使用流吗?

如果我理解正确,有三种流类型:

  • 读取流
  • 写入流

这是正确的吗?例如,MemoryStreamFileStream之间有什么区别?


14
你可能想要查看https://dev59.com/wXRB5IYBdhLWcg3wyqOo。该网页讨论了流的概念。 - Preets
3
取一个字节数组,然后创建一个包装器(Stream),它公开一些有用的方法,如读取、写入和更改位置。现在,您可以基于它们的后备存储(FileStream、MemoryStream)创建类,这些类继承自Stream并根据特定的后备存储构建该功能。 - The Muffin Man
8个回答

95

流(Stream)是用于传输数据的对象。在.NET中,所有其他流类都是从通用流类System.IO.Stream派生而来的。 Stream类处理字节。

具体的流类用于处理比字节更多类型的数据。例如:

  • FileStream类用于当外部源是文件时
  • MemoryStream用于将数据存储在内存中
  • System.Net.Sockets.NetworkStream处理网络数据

阅读器/编写器流,例如StreamReaderStreamWriter不是流 - 它们不是从System.IO.Stream派生的,它们旨在帮助从流中读取和写入数据!


3
所以,如果我理解正确的话,流(stream)包含数据但不对其进行任何操作。读取器和编写器“助手”类可以处理(操作)流中的数据? - Martijn
9
不,Stream不是数据容器,而是用于传输数据的,例如FileStream将数据从byte[]传输到物理文件,NetworkStream通过套接字传输byte[]。Reader Writer类是辅助类,用于从流中写入和读取数据,例如StreamReader可用于从Stream中读取字符串而非byte[]。如果将FileStream作为参数,则它将从文件中读取,如果是NetworkStream,则从套接字中读取。 - Arsen Mkrtchyan
此外,StreamReader 和 StreamWriter 用于读取和写入文本(字符)流。 - 1c1cle
1
这里有一篇很好的文章,可以帮助您了解MemoryStream。http://www.codeproject.com/Articles/832387/Using-MemoryStream-to-Wrap-Existing-Buffers-Gotcha - Jiaji Li
我不确定还有没有其他的例子,比如如果音频是动态数字化的话可能会有一个AudioStream,或者如果有一个专门的端口并且它可以在端口上动态数字化数据的话可能会有一个TemperatureStream。 - user420667
2
@user420667,好问题。在AudioStream和TemperatureStream的情况下,它们很可能都是与设备相关联的BinaryStreams。或者,您可以创建一个专门为接口构建的CustomStream。 - 1c1cle

66

针对其他回答进行一些补充,并帮助解释一些你会在代码示例中看到的东西,大多数情况下你不直接读写流。流是一种低级别的传输数据的方式。

你会注意到读写函数都是以字节为导向的,例如WriteByte()。没有处理整数、字符串等的函数。这使得流非常通用,但如果你只想传输文本,它就不那么容易使用。

然而,.NET提供了一些类来转换本地类型和底层流接口之间的数据,并为你传输数据到流或从流中传输数据。一些值得注意的类有:

StreamWriter // Badly named. Should be TextWriter.
StreamReader // Badly named. Should be TextReader.
BinaryWriter
BinaryReader

要使用这些类,首先需要获取您的数据流(stream),然后创建上述其中一个类,并将其与该数据流相关联。例如:

MemoryStream memoryStream = new MemoryStream();
StreamWriter myStreamWriter = new StreamWriter(memoryStream);

StreamReader 和 StreamWriter 可以在本地类型和它们的字符串表示之间进行转换,然后将这些字符串作为字节传输到流中并从流中读取。所以

myStreamWriter.Write(123);

将"123"(三个字符'1','2'和'3')写入流中。如果您正在处理文本文件(例如html),则应使用StreamReader和StreamWriter类。

myBinaryWriter.Write(123);

将写入四个字节,表示32位整数值123(0x7B、0x00、0x00、0x00)。如果您要处理二进制文件或网络协议,则可能会使用BinaryReader和BinaryWriter。(如果您正在与网络或其他系统交换数据,则需要注意字节序,但这是另一篇文章。)


StreamWriter和Reader适配器类的命名非常糟糕。感谢您提到这一点。他们为什么会想出这个名字,对我来说仍然很令人惊讶。 - Tarik
此外,即使是二进制写入器和读取器类的命名也很糟糕。 - Tarik

23

流处理适用于处理大量数据。当一次性将所有数据加载到内存中不现实时,可以将其作为流打开,并处理其中的小块数据。


4
很乐意为您提供所说的“使用小块进行工作”的示例。 - Jenna Leaf
2
流也适用于少量数据。如果C#程序员想要操作文件的内容,无论数据量大小,都必须使用流。对于网络流也是如此。当然,如果程序员使用像C这样的低级语言编码,则可以直接将字符或字节写入磁盘或套接字,但即使是少量数据,这也是耗时且容易出错的。 - 1c1cle

13

流(Stream)只是对一个字节流的抽象(或包装)。这个“物理”字节流被称为基础流(base stream)。因此,在创建流包装器时始终存在一个基础流,因此包装器以基础流类型命名,例如FileStream、MemoryStream等。

流包装器的优点在于,您可以获得统一的 API 与任何底层类型的流进行交互,例如USB、文件等。

为什么要将数据视为流? 因为数据块是按需加载的,我们可以将数据作为块进行检查/处理,而不是将整个数据加载到内存中。这就是大多数程序处理大型文件的方式,例如加密操作系统映像文件。


5

谢谢提供链接。我很喜欢那个“您可以在线浏览源代码,下载参考资料以供离线查看,并在调试期间逐步查看源代码(包括补丁和更新)”的功能。这个功能提供了新的洞察力水平。 - David

4

只有一种基本类型的Stream。但在不同的情况下,某些成员在调用时会抛出异常,因为在该上下文中操作不可用。

例如,MemoryStream只是一种将字节移入和移出内存块的方式。因此,您可以在其上调用Read和Write。

另一方面,FileStream允许您从/向文件读取或写入(或两者兼而有之)。是否可以实际进行读取或写入取决于打开文件的方式。如果您仅以读取访问权限打开文件,则无法对其进行写入。


1

我不会称它们为不同类型的流。Stream类有CanRead和CanWrite属性,告诉您特定流是否可读取和写入。

不同流类(例如MemoryStream vs FileStream)之间的主要区别在于后备存储器-从哪里读取数据或将其写入的位置。从名称上就很明显。MemoryStream仅将数据存储在内存中,FileStream由磁盘上的文件支持,NetworkStream从网络中读取数据等。


0
以下是一些来自microsoft的常用流类:
  • FileStream - 用于读写文件。
  • IsolatedStorageFileStream - 用于在隔离存储中读写文件。
  • MemoryStream - 用于将内存作为后备存储器进行读写。
  • BufferedStream - 用于提高读写操作的性能。
  • NetworkStream - 用于在网络套接字上进行读写。
  • PipeStream - 用于在匿名和命名管道上进行读写。
  • CryptoStream - 用于将数据流链接到加密转换。
MemoryStream通常适用于您需要处理相对较小量的数据,并且您知道内存的大小以及要加载到内存中的数据的大小。这样至少可以提高性能,因为您已经将所需数据存储在内存中,无需从磁盘访问。而当处理较大的文件时,FileStream更适合,因为可能无法将其完全加载到内存中。
对于"读取流"和"写入流",它们不是流的类型,而是System.IO命名空间提供的用于从流中读取编码字符和将其写入流的类型。这些类型包括StreamReader和StreamWriter。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接