什么是流?

问题描述 投票:100回答:6

编程世界中的流是什么?我们为什么需要它?

如果可能的话,请在类比的帮助下解释。

stream terminology
6个回答
136
投票

流表示一系列对象(通常是字节,但不一定是这样),可以按顺序访问。流上的典型操作:

  • 读一个字节。下次阅读时,您将获得下一个字节,依此类推。
  • 从流中读取几个字节到一个数组
  • seek(在流中移动当前位置,以便下次读取时从新位置获取字节)
  • 写一个字节
  • 将数组中的几个字节写入流中
  • 从流中跳过字节(这就像读取,但你忽略了数据。或者如果你喜欢它就像搜索但只能前进。)
  • 将字节推回到输入流中(这就像读取的“撤消”一样 - 你将几个字节备份到流中,这样下次你读到的就是你将看到的内容。它对于解析器来说偶尔会有用,因为:
  • peek(查看字节而不读取它们,以便它们仍然存在于流中以便稍后读取)

特定流可能支持读取(在这种情况下它是“输入流”),写入(“输出流”)或两者。并非所有流都可以搜索。

推回是相当罕见的,但您始终可以通过将实际输入流包装在另一个包含内部缓冲区的输入流中来将其添加到流中。读取来自缓冲区,如果您向后推,则数据将被放入缓冲区。如果缓冲区中没有任何内容,则推回流将从实际流中读取。这是“流适配器”的一个简单示例:它位于输入流的“末端”,它本身就是一个输入流,它执行的是原始流没有的额外功能。

Stream是一个有用的抽象,因为它可以描述文件(实际上是数组,因此搜索很简单),但也可以描述终端输入/输出(除非缓冲,它是不可搜索的),套接字,串行端口等。所以你可以编写代码说或者“我想要一些数据,我不关心它来自何处或如何到达”,或者“我会产生一些数据,这完全取决于我的调用者会发生什么”。前者采用输入流参数,后者采用输出流参数。

我能想到的最好的比喻是,流是一条传送带向你走来或远离你(或者有时两者)。你从输入流中取出东西,把东西放在输出流上。有些传送带,你可以想到从墙上的洞出来 - 它们不可寻找,阅读或写作是一次性的交易。有些传送带摆放在你面前,你可以随意选择想要读/写的流中的行踪 - 这就是寻求。

正如IRBMe所说,最好根据它提供的操作(从实现到实现,但有许多共同点)来考虑流,而不是通过物理类比。流是“你可以读或写的东西”。当您开始连接流适配器时,您可以将它们视为一个带有传送带的盒子和传送带,您连接到其他流,然后盒子对数据执行一些转换(压缩它或更改UNIX换行)对DOS,或其他)。管道是对比喻的另一个全面测试:在那里你创建一对流,这样你写入一个的任何东西都可以从另一个中读出。想想虫洞:-)


57
投票

流已经是一个比喻,类比,所以真的没有必要提供另一个。您可以将它基本上看作是一个带有水流的管道,其中水实际上是数据而管道是流。我认为如果流是双向的,它就是一种双向管道。它基本上是一种常见的抽象,放在一个或两个方向上有数据流或数据序列的事物上。

在诸如C#,VB.Net,C ++,Java等语言中,流隐喻用于许多事情。有文件流,您可以在其中打开文件,可以从流中读取或连续写入;存在网络流,其中对流的读取和写入对基础建立的网络连接进行读取和写入。仅用于写入的流通常称为输出流,如在this示例中,并且类似地,仅用于读取的流被称为输入流,如在this示例中那样。

流可以执行数据的转换或编码(例如,.Net中的SslStream会占用SSL协商数据并将其隐藏起来; TelnetStream可能会隐藏您的Telnet协商,但提供对数据的访问; A Java中的ZipOutputStream允许您写入zip存档中的文件,而不必担心zip文件格式的内部。

您可能会发现另一个常见的事情是文本流,它允许您编写字符串而不是字节,或者某些语言​​提供允许您编写基本类型的二进制流。您在文本流中可以找到的常见问题是字符编码,您应该注意这一点。

一些流也支持随机访问,如this示例中所示。另一方面,出于显而易见的原因,网络流不会。

UNIX操作系统也支持带有程序输入和输出的流模型,如here所述。


6
投票

除了上面提到的内容之外,还有一种不同类型的流 - 如函数式编程语言(如Scheme或Haskell)中所定义的 - 可能是无限的数据结构,它是由某个函数按需生成的。


6
投票

到目前为止给出的答案非常好。我只是提供另一个来强调流不是字节序列或特定于编程语言,因为这个概念是通用的(虽然它的实现可能是唯一的)。我经常在SQL或C或Java方面看到大量的在线解释,这些解释在文件流处理内存位置和低级别操作时都很有意义。但是他们经常讨论如何创建文件流并使用给定语言对潜在文件进行操作,而不是讨论流的概念。

The Metaphor

如上所述,stream是一个隐喻,是一个更复杂的东西的抽象。为了让你的想象力发挥作用,我提供了一些其他的比喻:

  1. 你想用水填充空池。实现此目的的一种方法是将软管连接到套管,将软管的末端放入池中并打开水。

软管是溪流

  1. 同样,如果你想给你的汽车补充气体,你可以去一个加油泵,将喷嘴插入油箱,然后挤压锁定杆打开阀门。

软管,喷嘴和允许气体流入水箱的相关机构就是水流

  1. 如果你需要上班,你会开始使用高速公路从家里开车到办公室。

高速公路是溪流

  1. 如果你想与某人交谈,你会用耳朵听,嘴巴说话。

你的耳朵和眼睛都是溪流

希望你在这些例子中注意到流隐喻只存在允许某些东西穿过它(或者在高速公路的情况下在它上面)而且它们本身并不构成他们正在转移的东西。一个重要的区别。我们不把我们的耳朵称为一系列单词。如果没有水流过软管,软管仍然是软管,但是我们必须将它连接到套管才能正常工作。汽车不是唯一可以穿越高速公路的“种类”汽车。

因此,只要连接到文件,就可以存在没有数据通过它的流。

Removing the Abstraction

接下来,我们需要回答几个问题。我将使用文件来描述流,所以...什么是文件?我们如何读取文件?我将尝试回答这个问题,同时保持一定程度的抽象以避免不必要的复杂性,并且由于其简单性和可访问性,将使用相对于Linux操作系统的文件概念。

什么是文件?

文件是抽象:)

或者,正如我可以解释的那样,文件是描述文件的一部分数据结构和一部分数据,它是实际内容。

数据结构部分(在UNIX / linux系统中称为inode)标识有关内容的重要信息,但不包括内容本身(或该文件的名称)。它保留的信息之一是内容开始的内存地址。因此,使用文件名(或Linux中的硬链接),文件描述符(操作系统关心的数字文件名)和内存中的起始位置,我们可以调用文件。

(关键的一点是'文件'是由操作系统定义的,因为它是最终必须处理它的操作系统。是的,文件要复杂得多)。

到现在为止还挺好。但是我们如何得到文件的内容,给你的男友写一封情书,以便我们打印出来?

读一个文件

如果我们从结果开始向后移动,当我们在计算机上打开文件时,它的全部内容都会在我们的屏幕上显示,以供我们阅读。但是怎么样?答案非常有条理。文件本身的内容是另一种数据结构。假设一个字符数组。我们也可以将其视为一个字符串。

那么我们如何'读'这个字符串呢?通过在内存中查找其位置并遍历我们的字符数组,一次一个字符,直到达到文件结束字符。换句话说,一个程序。

调用其程序时会“创建”流,并且它具有要连接或连接的内存位置。与我们的水管示例非常相似,如果软管没有连接到套管,则软管无效。对于流,它必须连接到文件才能存在。

可以进一步细化流,例如,用于接收输入的流或用于将文件内容发送到标准输出的流。 UNIX / linux连接并保持打开3个文件流,直接用于我们,stdin(标准输入),stdout(标准输出)和stderr(标准错误)。流可以构建为数据结构本身或对象,这允许我们通过它们执行数据流的更复杂操作,例如打开流,关闭流或错误检查流连接到的文件。 C ++的cin是一个流对象的例子。

当然,如果您这样选择,您可以编写自己的流。

Definition

流是一种可重复使用的代码,它提供了处理数据的复杂性,同时提供了对数据执行的有用操作。


5
投票

另一个类比:你不能游戏流,这就是为什么你可以从流中获取下一个位,字节,字符串或对象,同时删除已读取的数据。单程票......或者基本上只是一个没有存储持久性的队列。

那么我们需要排队吗?你决定。


4
投票

选择“流”这个词是因为它代表(在现实生活中)与我们在使用它时想要表达的非常相似的含义。

开始考虑对水流的类比。您可以获得连续的数据流,就像水在河流中不断流动一样。您不一定知道数据的来源,通常您不需要;无论是从文件,套接字还是任何其他来源,它都不应该(不应该)真正重要。这非常类似于接收水流,因此您无需知道它来自何处;无论是从湖泊,喷泉还是其他任何来源,它都不(不应该)真正重要。 source

© www.soinside.com 2019 - 2024. All rights reserved.