提问人:Rob Sobers 提问时间:2/4/2009 最后编辑:Hosam AlyRob Sobers 更新时间:9/20/2022 访问量:32695
你能解释一的概念吗?
Can you explain the concept of streams?
问:
我知道流是字节序列的表示形式。每个流都提供将字节读取和写入其给定后备存储的方法。但是流的意义何在?为什么后备存储本身不是我们与之交互的内容?
无论出于何种原因,这个概念对我来说都不是点击。我读过很多文章,但我想我需要一个类比什么的。
答:
流的重点是在您和后备存储之间提供抽象层。因此,使用流的给定代码块不需要关心后备存储是否是磁盘文件、内存等......
评论
它只是一个概念,另一个抽象层次,让你的生活更轻松。它们都具有通用接口,这意味着您可以以类似管道的方式组合它们。例如,编码为 base64,然后压缩,然后将其写入磁盘,所有这些都在一行中!
关键是你不应该知道后备存储是什么 - 它是对它的抽象。事实上,甚至可能没有后备存储 - 您可能从网络读取数据,并且数据根本不会被“存储”。
如果你能写出有效的代码,无论你是与文件系统、内存、网络还是其他任何支持流思想的东西通信,你的代码就会更加灵活。
此外,流通常链接在一起 - 您可以有一个流来压缩放入其中的任何内容,将压缩的形式写入另一个流,或者加密数据等。在另一端,会有反向链,解密,解压缩或其他什么。
评论
StreamReader
TextReader
BaseStream
流是字节序列的抽象。这个想法是,你不需要知道字节的来源,只需要以标准化的方式读取它们。
例如,如果通过流处理数据,那么数据是否来自文件、网络连接、字符串、数据库中的 blob 等对代码来说并不重要。
与后备存储本身交互本身并没有错,只是它将您与后备存储实现绑定在一起。
将流视为抽象数据源(字节、字符等)。它们抽象了读取和写入具体数据源的实际机制,无论是网络套接字、磁盘上的文件还是来自 Web 服务器的响应。
流是一种抽象,它提供了一组用于与数据交互的标准方法和属性。通过从实际的存储介质中抽象出来,可以编写代码,而无需完全依赖该介质是什么,甚至不依赖于该介质的实现。
一个很好的类比可能是考虑一个袋子。你不在乎一个袋子是用什么做的,也不在乎当你把你的东西放进去时它做了什么,只要袋子能起到袋子的作用,你就可以把你的东西拿出来。流为存储介质定义了袋子的概念,袋子的概念为袋子的不同实例(例如垃圾袋、手提袋、帆布背包等)定义了交互规则。
我认为你需要考虑的是,后备存储本身通常只是另一个抽象概念。内存流很容易理解,但文件会根据您使用的文件系统而完全不同,更不用说您使用的硬盘驱动器了。实际上,并非所有流都位于后备存储之上:网络流几乎只是流。
流的意义在于,我们将注意力限制在重要的事情上。通过具有标准抽象,我们可以执行常见操作。例如,即使您今天不想在文件或 HTTP 响应中搜索 URL,也不意味着您明天不想这样做。
流最初是在内存与存储相比很小的时候构思的。仅仅读取一个 C 文件可能是一个很大的负载。最小化内存占用非常重要。因此,几乎不需要加载的抽象非常有用。今天,它在执行网络通信时同样有用,事实证明,当我们处理文件时,它很少受到限制。以一般方式透明地添加缓冲等内容的能力使其更加有用。
为了添加到回音室,流是一个抽象,因此您不关心基础存储。当您考虑有流和没有流的方案时,它最有意义。
在大多数情况下,文件是无趣的,因为流的作用并没有超出我所熟悉的非基于流的方法。让我们从互联网文件开始。
如果我想从互联网上下载文件,我必须打开一个TCP套接字,建立连接,并接收字节,直到没有更多的字节。我必须管理一个缓冲区,知道预期文件的大小,并编写代码来检测连接何时断开并适当地处理。
假设我有某种 TcpDataStream 对象。我使用适当的连接信息创建它,然后从流中读取字节,直到它说没有更多的字节。该流处理缓冲区管理、数据结束条件和连接管理。
通过这种方式,流使 I/O 更容易。你当然可以编写一个 TcpFileDownloader 类来执行流所做的事情,但这样你就会有一个特定于 TCP 的类。大多数流接口仅提供 Read() 和 Write() 方法,任何更复杂的概念都由内部实现处理。因此,您可以使用相同的基本代码来读取或写入内存、磁盘文件、套接字和许多其他数据存储。
之所以选择“流”这个词,是因为它(在现实生活中)代表的含义与我们使用它时想要传达的含义非常相似。
让我们暂时忘记后备商店,并开始考虑与水流的类比。您会收到连续不断的数据流,就像水在河流中不断流动一样。您不一定知道数据来自哪里,而且大多数情况下您不需要知道;无论是来自文件、套接字还是任何其他来源,它都无关紧要。这与接收水流非常相似,因此您不需要知道它来自哪里;无论是来自湖泊、喷泉还是任何其他来源,它都无关紧要(不应该)。
也就是说,一旦你开始认为你只关心获得你需要的数据,不管它来自哪里,其他人谈论的抽象就会变得更加清晰。你开始认为你可以包装流,你的方法仍然会完美地工作。例如,您可以执行以下操作:
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());
看?只要你的方法不关心输入源是什么,你就可以通过各种方式自定义你的源。抽象允许您以一种非常优雅的方式将输入与处理逻辑分离。
请注意,我们自己创建的流没有后备存储,但它仍然完美地服务于我们的目的。
因此,总而言之,流只是一个输入源,隐藏(抽象)另一个源。只要你不破坏抽象,你的代码就会非常灵活。
评论
ReadInt
int.Parse
reader.ReadLine()
ReadString
Stream.Copy
我使用的可视化是传送带,不是在真实的工厂里,因为我对此一无所知,而是在卡通工厂里,物品沿着生产线移动,被盖章、装箱、计数和检查,由一系列愚蠢的设备检查。
你有简单的组件可以做一件事,例如,一个在蛋糕上放樱桃的设备。该设备具有无樱桃蛋糕的输入流和樱桃蛋糕的输出流。以这种方式构建您的处理有三个优点值得一提。
首先,它简化了组件本身:如果你想在蛋糕上放巧克力糖霜,你不需要一个复杂的设备来了解蛋糕的一切,你可以创建一个愚蠢的设备,将巧克力糖霜粘在里面的任何东西上(在卡通中,这甚至不知道下一个项目不是蛋糕, 它是 Wile E. Coyote)。
其次,你可以通过将设备按不同的顺序排列来创造不同的产品:也许你希望你的蛋糕在樱桃上涂上糖霜,而不是在糖衣上涂上樱桃,你可以简单地通过在生产线上交换设备来做到这一点。
第三,设备不需要管理库存、装箱或拆箱。最有效的聚合和包装方式是可变的:也许今天你把你的蛋糕装进 48 个盒子里,然后用卡车运出去,但明天你想寄出 6 个盒子来响应定制订单。这种变化可以通过在生产线的起点和终点更换或重新配置机器来适应;生产线中间的 Cherry Machine 不必更改一次处理不同数量的项目,它始终一次处理一个项目,并且不必知道其输入或输出是如何分组的。
我见过的对流的最好解释是 SICP 的第 3 章。(您可能需要阅读前 2 章才能理解它,但无论如何您都应该这样做。
他们根本不使用字节,而是使用整数。我从中得到的要点是:
- 流是延迟列表
- 计算开销[在某些情况下,急切地提前计算所有内容]是令人发指的
- 我们可以使用流来表示无限长的序列
评论
流已经是一个隐喻,一个类比,所以真的没有必要提供另一个。你基本上可以把它想象成一个管道,里面有水流,水实际上是数据,管道是溪流。如果流是双向的,我想它是一种 2 向管道。它基本上是一种常见的抽象,它被放置在一个或两个方向上存在数据流或序列的事物上。
在 C#、VB.Net、C++、Java 等语言中,流隐喻用于许多事情。有文件流,您可以在其中打开文件并可以从流中读取或连续写入;在网络流中,读取和写入流会读取和写入基础已建立的网络连接。仅用于写入的流通常称为输出流,如本示例所示,同样,仅用于读取的流称为输入流,如本示例所示。
流可以执行数据的转换或编码(例如,.Net 中的 SslStream 会占用 SSL 协商数据并将其隐藏起来;TelnetStream 可能会对您隐藏 Telnet 协商,但提供对数据的访问;Java 中的 ZipOutputStream 允许您写入 zip 存档中的文件,而不必担心 zip 文件格式的内部结构。
您可能会发现的另一个常见情况是允许您编写字符串而不是字节的文本流,或者某些语言提供允许您编写基元类型的二进制流。在文本流中,您会发现一个常见的东西是字符编码,您应该注意这一点。
某些流还支持随机访问,如本示例所示。另一方面,由于显而易见的原因,网络流不会。
- MSDN 很好地概述了 .Net 中的流。
- Sun 还概述了其常规的 OutputStream 类和 InputStream 类。
- 在 C++ 中,这里是 istream(输入流)、ostream(输出流)和 iostream(双向流)文档。
类似 UNIX 的操作系统也支持具有程序输入和输出的流模型,如此处所述。
流表示一系列对象(通常是字节,但不一定是字节),可以按顺序访问这些对象。对流的典型操作:
- 读取一个字节。下次读取时,你会得到下一个字节,依此类推。
- 将流中的多个字节读入数组
- Seek(移动您在流中的当前位置,以便下次读取时从新位置获取字节)
- 写入一个字节
- 将数组中的多个字节写入流
- 跳过流中的字节(这就像读取一样,但您忽略了数据。或者,如果您愿意,它就像寻求,但只能向前走。
- 将字节推回输入流中(这就像读取的“撤消” - 您将几个字节推回流中,以便下次读取时会看到。它偶尔对解析器有用,如下所示:
- 速览(在不读取字节的情况下查看字节,以便它们仍然在流中供以后读取)
特定流可能支持读取(在这种情况下,它是“输入流”)、写入(“输出流”)或两者兼而有之。并非所有流都是可搜索的。
推回是相当罕见的,但你总是可以通过将实际输入流包装在另一个包含内部缓冲区的输入流中来将其添加到流中。读取来自缓冲区,如果推回,则数据将放置在缓冲区中。如果缓冲区中没有任何内容,则推回流将从实际流中读取。这是“流适配器”的简单示例:它位于输入流的“末端”,它本身就是一个输入流,并且它执行原始流没有的额外操作。
Stream 是一个有用的抽象,因为它可以描述文件(实际上是数组,因此 seek 很简单),也可以描述终端输入/输出(除非缓冲,否则不可搜索)、套接字、串行端口等。因此,你可以编写代码,要么说“我想要一些数据,但我不在乎它来自哪里或它是如何到达这里的”,或者“我将生成一些数据,这完全取决于我的调用者会发生什么”。前者采用输入流参数,后者采用输出流参数。
我能想到的最好的比喻是,溪流是一条传送带,向你走来或远离你(有时两者兼而有之)。你从输入流中取出东西,你把东西放在输出流上。您可以认为有些传送带是从墙上的洞里出来的——它们无法找到,阅读或写作是一次性的。一些传送带摆在你面前,你可以沿着你想要读/写的流中的位置移动 - 这就是寻找。
然而,正如 IRBMe 所说,最好根据它提供的操作来考虑流(这些操作因实现而异,但有很多共同点),而不是通过物理类比来考虑。流是“您可以读取或写入的内容”。当您开始连接上行流适配器时,您可以将它们想象成一个带有传送带输入和传送带输出的盒子,您可以将其连接到其他流,然后该盒子对数据执行一些转换(压缩数据,或将 UNIX 换行符更改为 DOS 换行符,或其他什么)。管道是对这个比喻的另一个彻底测试:在这里,你创建了一对流,这样你写进去的任何内容都可以从另一个流中读出。想想虫洞:-)
评论
除了上面提到的内容之外,还有一种不同类型的流 - 如函数式编程语言(如 Scheme 或 Haskell)所定义的那样 - 一种可能无限的数据结构,由某些函数按需生成。
我会保持简短,我只是在这里遗漏了这个词:
流是队列,通常存储在包含任何类型数据的缓冲区中。
(现在,既然我们都知道什么是队列,就没有必要进一步解释了。
之所以选择“流”这个词,是因为它(在现实生活中)代表的含义与我们使用它时想要传达的含义非常相似。
开始思考水流的类比。您会收到连续不断的数据流,就像水在河流中不断流动一样。您不一定知道数据来自哪里,而且大多数情况下您不需要知道;无论是来自文件、套接字还是任何其他来源,它都无关紧要。这与接收水流非常相似,因此您不需要知道它来自哪里;无论是来自湖泊、喷泉还是任何其他来源,它都无关紧要(不应该)。源
当我第一次听说流媒体时,是在使用网络摄像头进行直播的背景下。因此,一个主持人正在广播视频内容,而另一个主持人正在接收视频内容。那么这是流媒体吗?井。。。是的。。。但直播是一个具体的概念,我认为这个问题指的是流媒体的抽象概念。查看 https://en.wikipedia.org/wiki/Live_streaming
因此,让我们继续前进。
视频不是唯一可以流式传输的资源。音频也可以流式传输。所以我们现在谈论的是流媒体。请参见 https://en.wikipedia.org/wiki/Streaming_media 。音频可以通过多种方式从源传递到目标。因此,让我们将一些数据传输方法相互比较。
经典文件下载经典文件下载不是实时进行的。在使用文件之前,您必须等到下载完成。
渐进式下载渐进式下载块将数据从流媒体文件下载到临时缓冲区。该缓冲区中的数据是可行的:缓冲区中的音频-视频数据是可播放的。因此,用户可以在下载时观看/收听流媒体文件。快进和快退是可能的,在缓冲区内偏离了方向。无论如何,渐进式下载不是直播。
流实时发生,并对数据进行分块。流式处理是在直播中实现的。收听广播的客户端无法快进或快退。在视频流中,数据在播放后被丢弃。
流式处理服务器与其客户端保持双向连接,而 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。
维基百科链接到这个: https://srfi.schemers.org/srfi-41/srfi-41.html 和作者对流有这样的看法:
流(有时称为惰性列表)是一种顺序数据结构 包含仅按需计算的元素。流为 null 或者是其 cdr 中带有流的一对。 由于流的元素是 仅在访问时计算,流可以是无限的。
因此,Stream 实际上是一种数据结构。
我的结论是:流是一个源,可以包含可以按顺序读取或写入的数据。流不会一次读取源包含的所有内容,而是按顺序读取/写入。
有用的链接:
- http://www.slideshare.net/auroraeosrose/writing-and-using-php-streams-and-sockets-zendcon-2011提供非常清晰的演示
- https://www.sk89q.com/2010/04/introduction-to-php-streams/
- http://www.netlingo.com/word/stream-or-streaming.php
- http://www.brainbell.com/tutorials/php/Using_PHP_Streams.htm
- http://www.sitepoint.com/php-streaming-output-buffering-explained/
- http://php.net/manual/en/wrappers.php
- http://www.digidata-lb.com/streaming/Streaming_Proposal.pdf
- http://www.webopedia.com/TERM/S/streaming.html
- https://en.wikipedia.org/wiki/Stream_%28computing%29
- https://srfi.schemers.org/srfi-41/srfi-41.html
另一点(对于读取文件的情况):
stream
可以让你在之前做点别的事情。finished reading all content of the file
- 您可以节省内存,因为不需要一次加载所有文件内容。
到目前为止给出的答案非常好。我只是提供另一个来强调流不是字节序列或特定于编程语言,因为这个概念是通用的(虽然它的实现可能是唯一的)。我经常在网上看到大量关于SQL、C或Java的解释,这些解释是有道理的,因为文件流处理的是内存位置和低级操作。但是他们经常讨论如何创建文件流并用给定的语言对潜在文件进行操作,而不是讨论流的概念。
隐喻
如前所述,a是一个隐喻,是更复杂事物的抽象。为了让你的想象力发挥作用,我提供了一些其他的比喻:stream
- 你想用水填满一个空池。实现此目的的一种方法是将软管连接到水龙头上,将软管的末端放入水池中并打开水。
软管就是溪流
- 同样,如果您想给汽车加油,您可以去加油泵,将喷嘴插入油箱并通过挤压锁定杆打开阀门。
软管、喷嘴和相关机构允许气体流入您的储罐就是气流
- 如果你需要去上班,你会开始从家里开车到办公室,使用高速公路。
高速公路就是溪流
- 如果你想和某人交谈,你会用你的耳朵去听,用你的嘴来说话。
你的耳朵和眼睛是溪流
希望您在这些示例中注意到,溪流隐喻的存在只是为了允许某些东西穿过它(或者在高速公路的情况下在它上面),并且它们本身并不总是提出它们正在传输的东西。一个重要的区别。我们不把我们的耳朵称为一连串的单词。如果没有水流过软管,软管仍然是软管,但我们必须将其连接到水龙头,以便它正确地完成工作。汽车并不是唯一可以穿越高速公路的“类型”车辆。
因此,只要连接到文件,就可以存在没有数据通过它的流。
删除抽象
接下来,我们需要回答几个问题。我将使用文件来描述流,所以......什么是文件?我们如何读取文件?我将尝试回答这个问题,同时保持一定程度的抽象以避免不必要的复杂性,并将使用相对于 linux 操作系统的文件的概念,因为它的简单性和可访问性。
什么是文件?
文件是一种抽象:)
或者,正如我可以简单地解释的那样,文件是描述文件的一部分数据结构和一部分数据,即实际内容。
数据结构部分(在 UNIX/linux 系统中称为 inode)标识有关内容的重要信息,但不包括内容本身(或文件的名称)。它保留的一条信息是内容开始位置的内存地址。因此,有了文件名(或linux中的硬链接),文件描述符(操作系统关心的数字文件名)和内存中的起始位置,我们就可以称之为文件。
(关键要点是“文件”是由操作系统定义的,因为最终必须处理它的操作系统是操作系统,是的,文件要复杂得多)。
目前为止,一切都好。但是我们如何获得文件的内容,比如给你的男朋友写一封情书,这样我们就可以打印出来呢?
读取文件
如果我们从结果开始并向后移动,当我们在计算机上打开一个文件时,它的全部内容都会溅到我们的屏幕上供我们阅读。但是如何?非常有条不紊地是答案。文件本身的内容是另一种数据结构。假设有一个字符数组。我们也可以将其视为字符串。
那么我们如何“读取”这个字符串呢?通过找到它在内存中的位置并遍历我们的字符数组,一次一个字符,直到到达文件字符的末尾。换句话说,一个程序。
当流的程序被调用时,流被“创建”,并且它有一个要附加或连接到的内存位置。就像我们的水管示例一样,如果软管没有连接到水龙头,则软管无效。对于流,它必须连接到文件才能存在。
可以进一步细化流,例如,接收输入的流或将文件内容发送到标准输出的流。UNIX/linux 立即为我们连接并保持开放的 3 个文件流,stdin(标准输入)、stdout(标准输出)和 stderr(标准错误)。流可以构建为数据结构本身或对象,这使我们能够对通过它们流的数据流执行更复杂的操作,例如打开流、关闭流或错误检查流连接到的文件。C++ 是流对象的一个示例。cin
当然,如果您愿意,您可以编写自己的流。
定义
流是一段可重用的代码,它抽象了处理数据的复杂性,同时提供了对数据执行的有用操作。
评论
流是一个高度抽象的隐喻,也是一个严格的契约。这意味着您可以按顺序操作对象,而不必担心间隙。也就是说,溪流必须没有真空或间隙。其中的对象一个接一个地按顺序连续排列。因此,我们不必担心在处理流的过程中突然遇到真空,或者我们不能在产生流时故意留下真空。换句话说,我们不必考虑在处理或生成流时出现空白的情况。我们不可能遇到它或故意生产它。如果要构建流,则不得在流中留任何间隙。
换句话说,如果有缺口,那一定不是流。当你将一个序列称为流时,要么你得到保证,其中没有间隙,要么你必须信守承诺,你产生的序列中没有间隙。
回顾一下,想想水流。它最突出的特点是什么?
连续的!
溪流抽象的精神就是关于它的。
评论