我理解流是字节序列的表示。每个流都提供了将字节读写到其给定的后备存储的方法。但溪流的意义何在?为什么后台存储本身不是我们交互的对象?

不知什么原因,我就是不喜欢这个概念。我读了很多文章,但我觉得我需要一个类比。


当前回答

流是字节序列的抽象。其思想是,您不需要知道字节来自何处,只需以标准化的方式读取它们。

例如,如果你通过流处理数据,那么数据来自文件、网络连接、字符串、数据库中的blob等等,对你的代码来说都无关紧要。

与备份存储本身交互本身并没有什么问题,除了它将您绑定到备份存储实现。

其他回答

之所以选择“流”这个词,是因为它(在现实生活中)与我们使用它时想要传达的意思非常相似。

Start thinking about the analogy to a water stream. You receive a continuous flow of data, just like water continuously flows in a river. You don't necessarily know where the data is coming from, and most often you don't need to; be it from a file, a socket, or any other source, it doesn't (shouldn't) really matter. This is very similar to receiving a stream of water, whereby you don't need to know where it is coming from; be it from a lake, a fountain, or any other source, it doesn't (shouldn't) really matter. source

流的目的是在您和后台存储之间提供一个抽象层。因此,使用流的给定代码块不需要关心后台存储是磁盘文件、内存等等…

我长话短说,我刚才漏掉了这个词:

流是通常存储在包含任何类型数据的缓冲区中的队列。

(现在,既然我们都知道队列是什么,就没有必要进一步解释了。)

为了增加回声室,流是一个抽象,所以您不关心底层存储。当您考虑有和没有流的场景时,这是最有意义的。

文件在很大程度上是无趣的,因为除了我熟悉的非基于流的方法之外,流并没有做太多事情。让我们从网络文件开始。

如果我想从互联网上下载一个文件,我必须打开一个TCP套接字,建立一个连接,并接收字节,直到没有更多的字节。我必须管理一个缓冲区,知道预期文件的大小,并编写代码来检测连接何时断开并适当地处理这个问题。

假设我有某种TcpDataStream对象。我用适当的连接信息创建它,然后从流中读取字节,直到它说没有任何字节。流处理缓冲区管理、数据结束条件和连接管理。

通过这种方式,流使I/O更容易。当然,您可以编写一个TcpFileDownloader类来完成流所做的工作,但是这样您就有了一个特定于TCP的类。大多数流接口只提供Read()和Write()方法,任何更复杂的概念都由内部实现处理。因此,您可以使用相同的基本代码来读写内存、磁盘文件、套接字和许多其他数据存储。

之所以选择“流”这个词,是因为它(在现实生活中)与我们使用它时想要传达的意思非常相似。

Let's forget about the backing store for a little, and start thinking about the analogy to a water stream. You receive a continuous flow of data, just like water continuously flows in a river. You don't necessarily know where the data is coming from, and most often you don't need to; be it from a file, a socket, or any other source, it doesn't (shouldn't) really matter. This is very similar to receiving a stream of water, whereby you don't need to know where it is coming from; be it from a lake, a fountain, or any other source, it doesn't (shouldn't) really matter.

也就是说,一旦您开始认为您只关心获得所需的数据,而不管数据来自何处,其他人谈论的抽象概念就会变得更加清晰。您开始认为可以包装流,并且您的方法仍然可以完美地工作。例如,你可以这样做:

int ReadInt(StreamReader reader) { return Int32.Parse(reader.ReadLine()); }

// in another method:
Stream fileStream = new FileStream("My Data.dat");
Stream zipStream = new ZipDecompressorStream(fileStream);
Stream decryptedStream = new DecryptionStream(zipStream);
StreamReader reader = new StreamReader(decryptedStream);

int x = ReadInt(reader);

如您所见,在不改变处理逻辑的情况下更改输入源变得非常容易。例如,要从网络套接字而不是文件读取数据:

Stream stream = new NetworkStream(mySocket);
StreamReader reader = new StreamReader(stream);
int x = ReadInt(reader);

尽可能的简单。而且美妙之处还在继续,因为您可以使用任何类型的输入源,只要您可以为它构建一个流“包装器”。你甚至可以这样做:

public class RandomNumbersStreamReader : StreamReader {
    private Random random = new Random();

    public String ReadLine() { return random.Next().ToString(); }
}

// and to call it:
int x = ReadInt(new RandomNumbersStreamReader());

看到了吗?只要您的方法不关心输入源是什么,您就可以以各种方式自定义源。抽象允许您以一种非常优雅的方式将输入与处理逻辑解耦。

请注意,我们自己创建的流没有备份存储,但它仍然完美地满足了我们的目的。

所以,总的来说,流只是一个输入源,隐藏(抽象)了另一个源。只要你不打破抽象,你的代码就会非常灵活。