【发布时间】:2010-11-16 00:39:06
【问题描述】:
什么是编程世界中的流?为什么我们需要它?
如果可能,请借助类比进行解释。
【问题讨论】:
标签: stream terminology
什么是编程世界中的流?为什么我们需要它?
如果可能,请借助类比进行解释。
【问题讨论】:
标签: stream terminology
流表示一系列对象(通常是字节,但不一定如此),可以按顺序访问。流上的典型操作:
一个特定的流可能支持读取(在这种情况下它是“输入流”)、写入(“输出流”)或两者兼而有之。并非所有流都是可搜索的。
推回是相当罕见的,但您始终可以通过将实际输入流包装在另一个包含内部缓冲区的输入流中来将其添加到流中。读取来自缓冲区,如果您向后推,则数据将放置在缓冲区中。如果缓冲区中没有任何内容,则推回流从实际流中读取。这是“流适配器”的一个简单示例:它位于输入流的“末端”,它本身就是一个输入流,它做了一些原始流没有做的额外事情。
Stream 是一个有用的抽象,因为它可以描述文件(实际上是数组,因此查找很简单),还可以描述终端输入/输出(除非缓冲,否则不可查找)、套接字、串行端口等。所以你可以写代码说“我想要一些数据,我不在乎它来自哪里或如何到达这里”,或者“我将产生一些数据,这完全取决于我的调用者会发生什么”。前者带输入流参数,后者带输出流参数。
我能想到的最好的类比是,溪流是一条传送带,向您驶来或远离您(或有时两者兼而有之)。你从输入流中取出东西,你把东西放在输出流上。有些传送带你可以想象成从墙上的一个洞里出来——它们是不可搜索的,读或写是一次性的。一些传送带布置在您面前,您可以在流中选择您想要读/写的位置 - 这就是寻找。
不过,正如 IRBMe 所说,最好根据它提供的操作(因实现而异,但有很多共同点)而不是通过物理类比来考虑流。流是“你可以读或写的东西”。当您开始连接上游适配器时,您可以将它们想象成一个盒子,其中有一个输入和输出,您连接到其他流,然后该盒子对数据执行一些转换(压缩它,或更改 UNIX 换行到 DOS 的,或其他的)。管道是对隐喻的另一个彻底测试:您可以在其中创建一对流,以便您写入其中的任何内容都可以从另一个中读出。想想虫洞:-)
【讨论】:
流已经是一个隐喻,一个类比,所以真的没有必要提供另一个。你基本上可以把它想象成一个管道,里面有水流,水实际上是数据,管道是流。如果流是双向的,我想它是一种双向管道。它基本上是一种常见的抽象,用于在一个或两个方向上存在数据流或序列的事物上。
在 C#、VB.Net、C++、Java 等语言中,流隐喻用于许多事情。有文件流,您可以在其中打开文件并可以从流中读取或连续写入;有网络流,其中读取和写入流读取和写入底层已建立的网络连接。仅用于写入的流通常称为输出流,如this 示例,类似地,用于只读的流称为输入流,如this 示例。
流可以执行数据的转换或编码(例如,.Net 中的 SslStream 会吃掉 SSL 协商数据并将其隐藏;TelnetStream 可能会对您隐藏 Telnet 协商,但提供访问权限到数据;Java 中的 ZipOutputStream 允许您写入 zip 存档中的文件,而不必担心 zip 文件格式的内部结构。
您可能会发现另一个常见的事情是允许您编写字符串而不是字节的文本流,或者某些语言提供允许您编写原始类型的二进制流。您会在文本流中发现一个常见的东西是字符编码,您应该注意这一点。
某些流还支持随机访问,如this 示例。另一方面,由于显而易见的原因,网络流不会。
类似 UNIX 的操作系统也支持带有程序输入和输出的流模型,如 here 所述。
【讨论】:
到目前为止给出的答案非常好。我只是提供另一个来强调流不是字节序列或特定于编程语言,因为这个概念是通用的(虽然它的实现可能是唯一的)。我经常在网上看到大量关于 SQL、C 或 Java 的解释,这很有意义,因为文件流处理内存位置和低级操作。但他们经常讨论如何创建文件流并以他们给定的语言对潜在文件进行操作,而不是讨论流的概念。
如前所述,stream 是一个隐喻,是对更复杂事物的抽象。为了让你发挥想象力,我提供了一些其他的比喻:
软管就是溪流
允许气体流入储罐的软管、喷嘴和相关机制是流
高速公路就是溪流
你的耳朵和眼睛是溪流
希望您在这些示例中注意到,流隐喻的存在只是为了允许某些东西通过它(或者在高速公路的情况下),并且它们本身并不总是构成它们正在转移的东西。一个重要的区别。我们不会将我们的耳朵称为一系列单词。如果没有水流过软管,软管仍然是软管,但我们必须将它连接到龙头上才能正常工作。汽车并不是唯一可以穿越高速公路的“种类”车辆。
因此,只要它连接到文件,就可以存在没有数据通过它的流。
接下来,我们需要回答几个问题。我将使用文件来描述流,所以... 什么是文件?我们如何读取文件?我将尝试回答这个问题,同时保持一定程度的抽象以避免不必要的复杂性,并将使用相对于 linux 操作系统的文件的概念,因为它的简单性和可访问性。
文件是一种抽象:)
或者,正如我可以解释的那样,文件是描述文件的一部分数据结构和一部分数据,即实际内容。
数据结构部分(在 UNIX/linux 系统中称为 inode)标识有关内容的重要信息片段,但不包括内容本身(或就此而言的文件名)。它保留的信息之一是内容开始的内存地址。因此,有了文件名(或 linux 中的硬链接)、文件描述符(操作系统关心的数字文件名)和内存中的起始位置,我们就有了可以称为文件的东西。
(关键是“文件”是由操作系统定义的,因为最终必须由操作系统来处理它。是的,文件要复杂得多)。
到目前为止一切顺利。但是我们如何获取文件的内容,给你的男朋友写一封情书,这样我们就可以打印出来了?
如果我们从结果开始并向后移动,当我们在计算机上打开一个文件时,它的全部内容都会显示在我们的屏幕上供我们阅读。但是怎么做?答案非常有条理。文件本身的内容是另一种数据结构。假设一个字符数组。我们也可以将其视为一个字符串。
那么我们如何“读取”这个字符串呢?通过找到它在内存中的位置并遍历我们的字符数组,一次一个字符,直到到达文件字符的末尾。换句话说,一个程序。
一个流在其程序被调用时被“创建”,并且它有一个内存位置可以附加到或连接到。就像我们的水管示例一样,如果软管没有连接到龙头,则它是无效的。在流的情况下,它必须连接到一个文件才能存在。
流可以进一步细化,例如,接收输入的流或将文件内容发送到标准输出的流。 UNIX/linux 立即为我们连接并保持打开 3 个文件流,stdin(标准输入)、stdout(标准输出)和 stderr(标准错误)。流可以构建为数据结构本身或对象,这允许我们通过它们执行更复杂的数据流操作,例如打开流、关闭流或错误检查流连接到的文件。 C++ 的cin 是流对象的一个示例。
当然,如果您愿意,您可以编写自己的流。
流是一段可重用的代码,它抽象了处理数据的复杂性,同时提供了对数据执行的有用操作。
【讨论】:
除了上面提到的东西之外,还有一种不同类型的流 - 如 Scheme 或 Haskell 等函数式编程语言中定义的那样 - 一种可能由某些函数按需生成的无限数据结构。
【讨论】:
之所以选择“流”这个词,是因为它代表(在现实生活中)与我们在使用它时想要传达的意思非常相似。
开始思考与水流的类比。您会收到持续不断的数据流,就像河流中不断流动的水一样。您不一定知道数据来自哪里,而且通常您也不需要;无论是来自文件、套接字还是任何其他来源,它都(不应该)真正重要。这与接收水流非常相似,您无需知道它来自哪里;无论是来自湖泊、喷泉还是任何其他来源,都(不应该)真的很重要。 source
【讨论】:
另一个类比:你不能逆流而上,这就是为什么你可以从流中获取下一个位、字节、字符串或对象,而已经读取的数据被删除。一张单程票...或者基本上只是一个队列,没有存储持久性。
那么我们需要排队吗?由你决定。
【讨论】: