我理解流是字节序列的表示。每个流都提供了将字节读写到其给定的后备存储的方法。但溪流的意义何在?为什么后台存储本身不是我们交互的对象?
不知什么原因,我就是不喜欢这个概念。我读了很多文章,但我觉得我需要一个类比。
我理解流是字节序列的表示。每个流都提供了将字节读写到其给定的后备存储的方法。但溪流的意义何在?为什么后台存储本身不是我们交互的对象?
不知什么原因,我就是不喜欢这个概念。我读了很多文章,但我觉得我需要一个类比。
当前回答
流的目的是在您和后台存储之间提供一个抽象层。因此,使用流的给定代码块不需要关心后台存储是磁盘文件、内存等等…
其他回答
关键是你不应该知道后台存储是什么——它只是一个抽象。实际上,甚至可能没有备份存储——您可能正在从网络中读取数据,而数据根本就没有“存储”。
如果你写的代码能够在文件系统、内存、网络或任何支持流思想的东西上工作,那么你的代码就会更加灵活。
此外,流通常是链接在一起的——你可以有一个流来压缩放入其中的任何内容,将压缩的表单写入另一个流,或者加密数据,等等。在另一端是反向链,解密,解压缩等等。
为了增加回声室,流是一个抽象,所以您不关心底层存储。当您考虑有和没有流的场景时,这是最有意义的。
文件在很大程度上是无趣的,因为除了我熟悉的非基于流的方法之外,流并没有做太多事情。让我们从网络文件开始。
如果我想从互联网上下载一个文件,我必须打开一个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());
看到了吗?只要您的方法不关心输入源是什么,您就可以以各种方式自定义源。抽象允许您以一种非常优雅的方式将输入与处理逻辑解耦。
请注意,我们自己创建的流没有备份存储,但它仍然完美地满足了我们的目的。
所以,总的来说,流只是一个输入源,隐藏(抽象)了另一个源。只要你不打破抽象,你的代码就会非常灵活。
这只是一个概念,另一个层次的抽象,让你的生活更容易。它们都有共同的接口,这意味着你可以以类似管道的方式组合它们。例如,编码到base64,然后压缩,然后将其写入磁盘,所有这些都在一行中!
The answers given so far are excellent. I'm only providing another to highlight that a stream is not a sequence of bytes or specific to a programming language since the concept is universal (while its implementation may be unique). I often see an abundance of explanations online in terms of SQL, or C or Java, which make sense as a filestream deals with memory locations and low level operations. But they often address how to create a filestream and operate on the potential file in their given language rather than discuss the concept of a stream.
这个比喻
如前所述,流是一种隐喻,是更复杂事物的抽象。为了激发你的想象力,我提供了一些其他的比喻:
你想把一个空池子装满水。实现这一目的的一种方法是将软管连接到水龙头上,将软管的一端放在水池中,然后打开水。
软管就是溪流
类似地,如果你想给你的车加油,你会走到加油站,把喷嘴插入油箱,然后通过挤压锁杆打开阀门。
软管,喷嘴和相关的机构,让气体流入你的油箱是流
如果你需要去上班,你会开始从家开车走高速公路到办公室。
高速公路就是溪流
如果你想和某人交谈,你会用耳朵听,用嘴巴说。
你的耳朵和眼睛是溪流
希望你在这些例子中注意到,流的隐喻只存在于允许某些东西通过它(或者在高速公路的情况下在它上面),而并不总是表示它们正在传输的东西。这是一个重要的区别。我们不认为耳朵是一连串的单词。如果没有水流经,软管仍然是软管,但我们必须将其连接到水龙头上,才能正确地工作。汽车并不是唯一一种可以穿越高速公路的交通工具。
因此,一个流可以存在,只要它连接到一个文件,它就没有数据通过它。
去除抽象
接下来,我们需要回答几个问题。我要用文件来描述流什么是文件?我们如何读取文件?我将尝试在保持一定抽象级别以避免不必要的复杂性的同时回答这个问题,并将使用相对于linux操作系统的文件概念,因为它的简单性和可访问性。
什么是文件?
文件是抽象的:)
或者,我可以简单地解释,一个文件是描述文件的一部分数据结构和实际内容的一部分数据。
数据结构部分(在UNIX/linux系统中称为inode)标识关于内容的重要信息,但不包括内容本身(或文件的名称)。它保留的信息之一是内容开始位置的内存地址。因此,有了文件名(或linux中的硬链接)、文件描述符(操作系统关心的数字文件名)和内存中的起始位置,我们就有了可以称为文件的东西。
(关键是“文件”是由操作系统定义的,因为最终必须处理它的是操作系统。是的,文件要复杂得多)。
到目前为止一切顺利。但我们怎么拿到文件的内容,比如给你男友的情书,这样我们就能打印出来了?
读取文件
如果我们从结果开始并向后移动,当我们在计算机上打开一个文件时,它的全部内容都会显示在屏幕上供我们阅读。但如何?答案是非常有条理。文件本身的内容是另一种数据结构。假设有一个字符数组。我们也可以把它看成一个字符串。
那么我们如何“读取”这个字符串呢?通过找到它在内存中的位置并遍历我们的字符数组,一次一个字符,直到到达文件字符的末尾。换句话说就是一个程序。
当流的程序被调用时,流就被“创建”了,并且它有一个可以附加到或连接到的内存位置。就像我们的水管的例子一样,如果软管没有连接到水龙头上,它是无效的。在流的情况下,它必须连接到文件才能存在。
Streams can be further refined, e.g, a stream to receive input or a stream to send a files contents to standard output. UNIX/linux connects and keeps open 3 filestreams for us right off the bat, stdin (standard input), stdout (standard output) and stderr (standard error). Streams can be built as data structures themselves or objects which allows us to perform more complex operations of the data streaming through them, like opening the stream, closing the stream or error checking the file a stream is connected to. C++'s cin is an example of a stream object.
当然,如果您愿意,您可以编写自己的流。
定义
流是一段可重用的代码,它抽象了处理数据的复杂性,同时提供了对数据执行的有用操作。