您能解释一下流的概念吗?


186

我知道流是字节序列的表示。每个流都提供用于将字节读写到其给定的后备存储的方法。但是流的意义是什么?为什么我们与之交互的后备商店本身不存在?

不管出于什么原因,这个概念都无法满足我的需求。我读了很多文章,但我想我需要一个类比。

Answers:


234

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

让我们稍微忘记一下后备店,然后开始考虑与水流的类比。您会收到连续的数据流,就像水在河中不断流动一样。您不必知道数据来自何处,而且通常不需要。无论是来自文件,套接字还是其他任何来源,都没有(应该)没有关系。这与接收水流非常相似,您无需知道水的来源。无论是来自湖泊,喷泉还是其他任何来源,都没有(应该)没有关系。

就是说,一旦您开始考虑只关心获取所需数据(无论它来自何处),其他人谈论的抽象就会变得更加清晰。您开始认为可以包装流,并且您的方法仍然可以完美地工作。例如,您可以这样做:

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());

看到?只要您的方法不关心输入源是什么,就可以通过各种方式自定义源。抽象允许您以非常优雅的方式将输入与处理逻辑解耦。

请注意,我们自己创建的流没有后备存储,但仍然可以完美地满足我们的目的。

因此,总而言之,流只是输入的源,而隐藏(抽象)了另一个源。只要不破坏抽象,您的代码就会非常灵活。


6
抽象的思想(和解释)似乎已经渗入您的血液;)您对水的类比(以及隐喻性的引用)使我想起了奥马尔·海亚姆。
java.is.for.desktop,2010年

@HosamAly您的解释很清楚,但示例代码中有些地方使我感到困惑。从字符串到int的显式转换是自动执行ReadInt吗?我相信我也可以做ReadString吗?
鲁西诺(Rushino)2012年

1
@Rushino上面的代码中没有转换。该方法ReadInt在最顶部使用定义,该方法int.Parse接收从返回的字符串reader.ReadLine()并对其进行解析。当然,您可以创建类似的ReadString方法。这足够清楚吗?
Hosam Aly 2012年

说得好。在我看来,流是整个编程中最简单,功能最强大的泛型抽象。使用.net basic Stream.Copy可以使许多应用程序的工作变得更加轻松。
Felype

38

关键是您不必知道后备存储是什么-它是对它的抽象。确实,可能甚至没有一个后备存储-你可以从网络上阅读,而数据永远不会“存储”在所有。

如果您编写的代码无论与文件系统,内存,网络还是其他支持流思想的代码都可以使用,那么您的代码将更加灵活。

此外,流通常被链接在一起-您可以拥有一个流,该流可以压缩放入其中的任何内容,将压缩后的形式写到另一个流上,或者将一个加密的数据加密,等等。另一方面,链,解密,解压缩等。


上面@HosamAly示例中使用的不同类型的流读取器是否不表示您确实知道后备存储是什么?我认为FileStream,NetworkStream等...从这些类型的源中读取。此外,在某些情况下,您可能不知道什么是后备存储,而在程序运行时会动态选择该存储?我只是个人没有遇到过,想了解更多。
user137717

另外,可以在生成数据时通过某种流程流式传输数据,还是在开始流程时需要访问要操作的完整数据集?
user137717

@ user137717:不,如果您只使用StreamReader-或更好,TextReader则您的代码不知道哪种数据流是数据流的基础。或者更确切地说,它可以使用该BaseStream属性来找出类型-但它可能是您的代码从未见过的类型。关键是您不必在意。是的,您绝对可以编写出有时会用于网络流和有时会用于文件流的代码。至于流通过流程管道传输数据- 流程内部不会完成...它将是流提供程序。
乔恩·斯基特

30

流的重点是在您和后备存储之间提供抽象层。因此,使用流的给定代码块不必关心后备存储是否是磁盘文件,内存等。


是的,它允许您在不破坏代码的情况下交换流的类型。例如,您可以在一个调用中读取文件,然后在下一个调用中读取内存缓冲区。
克雷格(Craig)

我要补充一下,您这样做的原因是,在读取或写入文件时,通常不需要文件查找功能,因此,如果使用流,则可以轻松地使用相同的代码读取或写入文件。例如网络插座。
alxp

11

这与溪流无关-与游泳有关。如果您可以畅游一条溪流,那么就可以畅游遇到的任何溪流。


7

要添加到回显室,流是一种抽象,因此您不必关心基础存储。考虑带流和不带流的方案时,这是最有意义的。

文件在大多数情况下是无趣的,因为流在​​我所熟悉的非基于流的方法之上和之外没有做太多事情。让我们从Internet文件开始。

如果要从Internet下载文件,则必须打开TCP套接字,建立连接并接收字节,直到没有更多字节为止。我必须管理一个缓冲区,知道所需文件的大小,并编写代码以检测何时断开连接并适当地处理它。

假设我有某种TcpDataStream对象。我用适当的连接信息创建它,然后从流中读取字节,直到它说不再有字节为止。流处理缓冲区管理,数据结束条件和连接管理。

这样,流使I / O更容易。您当然可以编写一个TcpFileDownloader类来完成流的工作,但是然后有了一个特定于TCP的类。大多数流接口仅提供Read()和Write()方法,任何更复杂的概念都由内部实现处理。因此,您可以使用相同的基本代码来读取或写入内存,磁盘文件,套接字和许多其他数据存储。


5

我使用的可视化是传送带,不是在真正的工厂中,因为我对此一无所知,但在卡通工厂中,物品沿线移动并被盖章,装箱并由一系列笨拙的设备进行计数和检查。

您具有完成某件事的简单组件,例如,将樱桃放在蛋糕上的设备。该设备具有无樱桃蛋糕的输入流和带有樱桃的蛋糕的输出流。值得一提的是,以这种方式结构化处理具有三个优点。

首先,它可以简化组件本身:如果您想将巧克力糖衣放在蛋糕上,则不需要复杂的设备就可以了解蛋糕的所有知识,您可以创建一个笨拙的设备,将巧克力糖衣粘贴到进料的任何东西上(在这些动画片,甚至不知道其中的下一个不是蛋糕,而是Wile E. Coyote)。

其次,您可以通过将设备按不同的顺序放置来创建不同的产品:也许您希望您的蛋糕在樱桃上有糖衣,而不是在樱桃上糖衣,您只需在生产线上交换设备就可以做到这一点。

第三,设备不需要管理库存,装箱或拆箱。聚合和包装物品的最有效方法是可变的:也许今天您将蛋糕放入48盒的盒子中,然后用卡车将其发送出去,但是明天您想响应自定义订单而发出6盒的盒子。可以通过在生产线的起点和终点更换或重新配置机器来适应这种变化。该行中间的cherry机器不必更改为一次处理不同数量的项目,它始终可以一次处理一个项目,并且不必知道其输入或输出方式被分组。


类比解释的一个很好的例子。
Richie Thomas

5

当我第一次听说流媒体时,是在使用网络摄像头进行实时流媒体的情况下。因此,一个主机正在广播视频内容,而另一主机正在接收视频内容。那么这是流媒体吗?嗯...是的...但是直播是一个具体的概念,我认为这个问题涉及流媒体的抽象概念。参见https://en.wikipedia.org/wiki/Live_streaming

因此,让我们继续前进。


视频不是唯一可以流式传输的资源。音频也可以流式传输。因此,我们现在正在谈论流媒体。请参阅https://en.wikipedia.org/wiki/Streaming_media。音频可以通过多种方式从源传送到目标。因此,让我们相互比较一些数据传递方法。

经典文件下载 经典文件下载不是实时发生的。在使用该文件之前,您必须等待下载完成。

渐进式下载 渐进式下载块将数据从流媒体文件下载到临时缓冲区。该缓冲区中的数据是可行的:缓冲区中的音频视频数据是可播放的。因此,用户可以在下载时观看/收听流媒体文件。快进和倒带是可能的,并且在缓冲区外也可以进行。无论如何,渐进式下载不是实时流式传输。

流式传输 实时发生,并分块数据。流是在直播中实现的。收听广播的客户无法快进或快退。在视频流中,播放后会丢弃数据。

流服务器与客户端保持2通连接,而Web服务器在服务器响应后关闭连接。


音频和视频不是唯一可以流式传输的内容。让我们看一下PHP手册中流的概念。

流是显示可流行为的资源对象。也就是说,它可以线性方式读取写入,并且可以fseek()到流中的任意位置。链接:https//www.php.net/manual/en/intro.stream.php

在PHP中,资源是对外部源(例如文件,数据库连接)的引用。因此,换句话说,流是可以读取或写入的源。因此,如果您使用过fopen(),那么您已经使用过流。

流式处理的文本文件的示例:

// Let's say that cheese.txt is a file that contains this content: 
// I like cheese, a lot! My favorite cheese brand is Leerdammer.
$fp = fopen('cheese.txt', 'r');

$str8 = fread($fp, 8); // read first 8 characters from stream. 

fseek($fp, 21); // set position indicator from stream at the 21th position (0 = first position)
$str30 = fread($fp, 30); // read 30 characters from stream

echo $str8; // Output: I like c 
echo $str30; // Output: My favorite cheese brand is L

Zip文件也可以流式传输。最重要的是,流媒体不仅限于文件。HTTP,FTP,SSH连接和输入/输出也可以流式传输。


维基百科对流媒体的概念怎么说?

在计算机科学中,流是随时间推移而可用的一系列数据元素。可以将流视为一次处理一条传送带上的物品,而不是大批量处理。

请参阅:https : //en.wikipedia.org/wiki/Stream_%28computing%29

Wikipedia链接到此:https : //srfi.schemers.org/srfi-41/srfi-41.html ,作者对流有这样的说法:

流(有时称为惰性列表)是一种顺序数据结构,其中包含仅按需计算的元素。流为null或在其cdr中与流成对。由于仅在访问流时才计算流的元素,因此流可以是无限的。

因此,Stream实际上是一种数据结构。


我的结论是:流是一种源,可以包含可以按顺序读取或写入的数据。流不会一次读取源包含的所有内容,而是顺序读取/写入。


有用的链接:

  1. http://www.slideshare.net/auroraeosrose/writing-and-using-php-streams-and-sockets-zendcon-2011提供了非常清晰的演示
  2. https://www.sk89q.com/2010/04/introduction-to-php-streams/
  3. http://www.netlingo.com/word/stream-or-streaming.php
  4. http://www.brainbell.com/tutorials/php/Using_PHP_Streams.htm
  5. http://www.sitepoint.com/php-streaming-output-buffering-explained/
  6. http://php.net/manual/zh/wrappers.php
  7. http://www.digidata-lb.com/streaming/Streaming_Proposal.pdf
  8. http://www.webopedia.com/TERM/S/streaming.html
  9. https://zh.wikipedia.org/wiki/Stream_%28computing%29
  10. https://srfi.schemers.org/srfi-41/srfi-41.html

4

这只是一个概念,另一个抽象层次使您的生活更加轻松。它们都有通用的接口,这意味着您可以通过管道方式将它们组合在一起。例如,编码为base64,然后压缩,然后将其写入磁盘,然后全部一行!


当然,这很有用,但我不会说这是“重点”。即使没有链接,具有通用抽象也是很有用的。
乔恩·斯基特

是啊,你说得对。我更改了措辞以使其更清楚。
vava

是的,那更好。希望你不认为我太挑剔!
乔恩·斯基特

3

我见过的关于流的最佳解释是SICP的第3章。(为了使它有意义,您可能需要阅读前两章,但是无论如何,您应该。:-)

他们根本不使用steram来表示字节,而是使用整数。我从中得到的要点是:

  • 流是延迟列表
  • [在某些情况下,急切地进行所有事情的计算]的计算开销是惊人的
  • 我们可以使用流来表示无限长的序列

实际上,我目前正在SICP的第一章。谢谢!
罗布·索伯斯

2
一个人想告诉别人SICP流。的一个重要特征SICP流懒惰,而通用概念强调的抽象的数据序列
象嘉道

2

另一点(用于读取文件的情况):

  1. stream可以让你做其他的事情finished reading all content of the file
  2. 您可以节省内存,因为不需要一次加载所有文件内容。

1

将流视为抽象的数据源(字节,字符等)。他们抽象了读取和写入具体数据源的实际机制,包括网络套接字,磁盘上的文件或Web服务器的响应。


1

我认为您需要考虑到后备存储本身通常只是另一种抽象。内存流很容易理解,但是文件会因所使用的文件系统而有根本不同,不必管您使用的是哪个硬盘驱动器。实际上,并非所有流都位于后备存储的顶部:网络流几乎就是流。

交流的重点是我们将注意力集中在重要的事情上。通过具有标准的抽象,我们可以执行常见的操作。举例来说,即使您今天不想在文件或HTTP响应中搜索URL,也并不意味着您就不会希望明天。

流最初是在内存与存储相比很小的时候构想的。仅读取C文件可能是一个很大的负担。最小化内存占用非常重要。因此,非常需要加载的抽象非常有用。如今,它在执行网络通信时同样有用,事实证明,在处理文件时,这种限制很少。以通用方式透明地添加诸如缓冲之类的功能的能力使其更加有用。


0

流是字节序列的抽象。这样的想法是,您不需要知道字节从何而来,只需以标准化的方式读取它们即可。

例如,如果您通过流处理数据,则数据与文件,网络连接,字符串,数据库中的Blob等无关,与代码无关紧要。

与后备存储本身进行交互本身没有任何错误,除了它将您与后备存储实现绑定在一起。


0

流是一种抽象,它提供用于与数据交互的标准方法和属性集。通过从实际的存储介质中抽象出来,可以编写代码而无需完全依赖于该介质是什么,甚至不依赖于该介质的实现。

一个很好的类比是考虑一个袋子。您不必担心袋子的制造或放入袋子时的工作,只要袋子能够作为袋子工作,并且您可以将其取出。流为存储介质定义了袋的概念为袋的不同实例(例如垃圾袋,手提袋,背包等)定义的相互作用规则。


0

我会简短地说,我只是在这里漏掉了这个词:

流是通常存储在包含任何类型数据的缓冲区中的队列

(现在,由于我们都知道什么是队列,因此无需进一步解释。)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.