什么是流?

pok*_*ate 114 terminology stream

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

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

Ste*_*sop 144

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

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

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

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

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

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

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

  • 到目前为止,我读过的最好的解释。再加上SICP中所说的话(“流处理使我们可以对具有状态的系统进行建模,而无需使用赋值或可变数据。”),我想我终于明白了。谢谢! (3认同)

IRB*_*BMe 66

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

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

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

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

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

UNIX类的操作系统也支持与程序的输入和输出的流模型,如所描述这里.


rek*_*ion 8

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

隐喻

如前所述,a stream是一个隐喻,是对更复杂事物的抽象。为了发挥您的想象力,我提供了其他一些隐喻:

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

软管是溪流

  1. 同样,如果您想给汽车加气,则需要去加气泵,将喷嘴插入储气罐,然后通过挤压锁定杆打开阀门。

软管,喷嘴和相关的机制可以使气体流进油箱

  1. 如果您需要上班,则可以使用高速公路从家中开车到办公室。

高速公路是溪流

  1. 如果您想与某人交谈,您将用耳朵听,用嘴巴讲话。

你的耳朵和眼睛是溪流

希望您在这些示例中注意到,流隐喻仅存在于允许某物通过它的情况下(或在高速公路的情况下),并且它们本身并不总是摆在它们要传递的事物上。一个重要的区别。我们不是把耳朵说成是一个单词序列。如果没有水流过,软管仍然是软管,但是我们必须将其连接到水龙头,才能正确完成其工作。汽车并不是唯一可以穿越高速公路的“种类”车辆。

因此,一个流可以存在没有数据通过它,只要它行驶连接到一个文件

删除抽象

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

什么是文件?

文件是一个抽象:)

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

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

(关键要点是操作系统定义的“文件”,因为最终要处理该文件的是操作系统。是的,文件要复杂得多)。

到目前为止,一切都很好。但是,我们如何获得文件的内容,给您的爱人说一封情书,以便我们进行打印?

读取文件

如果我们从结果开始然后向后移动,则当我们在计算机上打开文件时,其所有内容都会溅到屏幕上供我们阅读。但是如何?答案很有条理。文件本身的内容是另一个数据结构。假设一个字符数组。我们也可以将其视为字符串。

那么,我们如何“读取”此字符串?通过找到其在内存中的位置并遍历我们的字符数组,一次可以一个字符直到到达文件末尾。换句话说就是一个程序。

当流的程序被调用时,将“创建”该流,并且该流具有要附加连接到的内存位置。就像我们的水管示例一样,如果未连接水龙头,则该水管无效。对于流,它必须连接到文件才能存在。

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

当然,如果您愿意,可以编写自己的流。

定义

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

  • 我目前正在学习 Java,你的回答帮助我理解了它。 (2认同)

Pre*_*raj 7

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

开始考虑与水流的类比。您会收到源源不断的数据流,就像河流中不断流淌的水一样。您不一定知道数据来自哪里,而且大多数情况下您不需要知道;无论是来自文件、套接字还是任何其他来源,它都(不应该)真正重要。这与接收水流非常相似,您无需知道它来自哪里;无论是来自湖泊、喷泉还是任何其他来源,它都(不应该)真正重要。来源


EFr*_*aim 6

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


Mar*_*cus 5

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

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