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