【发布时间】:2010-10-05 04:45:18
【问题描述】:
我了解流是字节序列的表示。每个流都提供了读取和写入字节到其给定后备存储的方法。但是流的意义何在?为什么我们与之交互的不是后备存储本身?
无论出于何种原因,这个概念对我来说并不合适。我读过很多文章,但我想我需要一个类比什么的。
【问题讨论】:
标签: java .net stream language-agnostic iostream
我了解流是字节序列的表示。每个流都提供了读取和写入字节到其给定后备存储的方法。但是流的意义何在?为什么我们与之交互的不是后备存储本身?
无论出于何种原因,这个概念对我来说并不合适。我读过很多文章,但我想我需要一个类比什么的。
【问题讨论】:
标签: java .net stream language-agnostic iostream
之所以选择“流”这个词,是因为它代表(在现实生活中)与我们在使用它时想要传达的意思非常相似。
让我们暂时忘记后备存储,并开始考虑与水流的类比。您会收到持续不断的数据流,就像河流中不断流动的水一样。您不一定知道数据来自哪里,而且大多数情况下您也不需要;无论是来自文件、套接字还是任何其他来源,它都(不应该)真的很重要。这与接收水流非常相似,您无需知道它来自哪里;无论是来自湖泊、喷泉还是任何其他来源,都(不应该)真正重要。
也就是说,一旦您开始认为您只关心获取所需的数据,而不管数据来自何处,其他人谈论的抽象就会变得更加清晰。你开始认为你可以包装流,你的方法仍然可以完美地工作。例如,您可以这样做:
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 让许多应用程序的生活变得如此轻松。
关键是您不必知道后备存储是什么——它是对它的抽象。实际上,甚至可能没有后备存储 - 您可能正在从网络读取数据,而数据根本不会“存储”。
如果您编写的代码无论您是在与文件系统、内存、网络还是其他任何支持流概念的事物对话时都能正常工作,那么您的代码就会更加灵活。
此外,流通常链接在一起 - 您可以有一个流来压缩放入其中的任何内容,将压缩形式写入另一个流,或加密数据等。在另一端有是反向链,解密,解压什么的。
【讨论】:
StreamReader - 或者更好的是TextReader,那么您的代码不知道数据流是哪种流。或者更确切地说,它可以使用BaseStream 属性来找出类型——但它可能是您的代码以前从未见过的类型。关键是你不应该关心。是的,您可以绝对最终编写代码,这些代码有时用于网络流,有时用于文件流。至于通过流程传输数据的流 - 那么这不会在流程内部完成......它将是流提供者。
流的意义在于在您和后备存储之间提供一个抽象层。因此,使用流的给定代码块不需要关心后备存储是否是磁盘文件、内存等......
【讨论】:
这与溪流无关 - 与游泳有关。如果你能游过一条溪流,那么你就可以游过任何遇到的溪流。
【讨论】:
要添加到回声室,流是一种抽象,因此您不必关心底层存储。当您考虑有和没有流的场景时,它最有意义。
文件在很大程度上是无趣的,因为流并没有超出我熟悉的非基于流的方法所做的太多。让我们从互联网文件开始。
如果我想从 Internet 下载文件,我必须打开 TCP 套接字,建立连接并接收字节,直到没有更多字节为止。我必须管理一个缓冲区,知道预期文件的大小,并编写代码来检测连接何时断开并适当地处理。
假设我有某种 TcpDataStream 对象。我使用适当的连接信息创建它,然后从流中读取字节,直到它说没有更多字节了。流处理缓冲区管理、数据结束条件和连接管理。
通过这种方式,流使 I/O 变得更容易。您当然可以编写一个 TcpFileDownloader 类来执行流所做的事情,但是您有一个特定于 TCP 的类。大多数流接口只是提供了 Read() 和 Write() 方法,任何更复杂的概念都由内部实现处理。因此,您可以使用相同的基本代码来读取或写入内存、磁盘文件、套接字和许多其他数据存储。
【讨论】:
我使用的可视化是传送带,不是在真实的工厂中,因为我对此一无所知,而是在卡通工厂中,物品沿着线条移动,并被一系列愚蠢的设备盖章、装箱、计数和检查。
您有一些简单的组件可以做一件事,例如将樱桃放在蛋糕上的设备。该设备具有无樱桃蛋糕的输入流和带有樱桃的蛋糕的输出流。以这种方式构建处理结构有三个优点值得一提。
首先它简化了组件本身:如果你想在蛋糕上涂上巧克力糖衣,你不需要一个了解蛋糕一切的复杂设备,你可以创建一个愚蠢的设备,将巧克力糖衣粘在任何东西上它(在漫画中,这甚至不知道下一个项目不是蛋糕,而是 Wile E. Coyote)。
其次,您可以通过将设备放入不同的序列来创建不同的产品:也许您希望您的蛋糕在樱桃上涂上糖霜,而不是在糖衣上涂上樱桃,您可以简单地通过交换设备来做到这一点在线上。
第三,设备不需要管理库存、装箱或拆箱。聚合和包装事物的最有效方式是多变的:也许今天您将蛋糕放入 48 个一盒的盒子中,然后用卡车运送出去,但明天您想根据定制订单发送 6 个一盒的蛋糕。这种变化可以通过更换或重新配置生产线起点和终点的机器来适应;无需更改生产线中间的樱桃机即可一次处理不同数量的项目,它始终一次处理一个项目,并且不必知道其输入或输出如何正在分组。
【讨论】:
当我第一次听说流媒体时,它是在带有网络摄像头的实时流媒体的背景下。因此,一台主机正在广播视频内容,而另一台主机正在接收视频内容。那么这是流媒体吗?嗯……是的……但是直播是一个具体的概念,我认为这个问题是指流媒体的抽象概念。见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 作者对流有这样的看法:
流,有时称为惰性列表,是一种顺序数据结构 包含仅按需计算的元素。流要么为空 或者是一对在其 cdr 中有一个流。由于流的元素是 仅在访问时计算,流可以是无限的。
所以 Stream 实际上是一种数据结构。
我的结论:流是可以包含可以按顺序读取或写入的数据的源。流不会立即读取源包含的所有内容,它会按顺序读取/写入。
有用的链接:
【讨论】:
这只是一个概念,是另一个抽象层次,让您的生活更轻松。它们都有共同的界面,这意味着您可以将它们以类似管道的方式组合起来。例如,编码为base64,然后压缩,然后将其写入磁盘,全部在一行中!
【讨论】:
我见过的关于流的最好解释是chapter 3 of SICP。 (您可能需要阅读前两章才能理解,但无论如何您都应该阅读。:-)
他们根本不使用 sterams 来表示字节,而是使用整数。我从中得到的要点是:
【讨论】:
另外一点(对于读取文件的情况):
stream 可以让你在finished reading all content of the file 之前做其他事情。【讨论】:
将流视为抽象的数据源(字节、字符等)。它们抽象了读取和写入具体数据源的实际机制,无论是网络套接字、磁盘上的文件还是来自 Web 服务器的响应。
【讨论】:
我认为您需要考虑后备存储本身通常只是另一种抽象。内存流很容易理解,但文件完全不同,具体取决于您使用的文件系统,更不用说您使用的硬盘驱动器了。并非所有流实际上都位于后备存储之上:网络流几乎就是流。
流的意义在于我们将注意力限制在重要的事情上。通过有一个标准的抽象,我们可以执行常见的操作。例如,即使您今天不想在文件或 HTTP 响应中搜索 URL,也不意味着您明天不希望这样做。
流最初是在内存相对于存储空间很小的时候被构思出来的。仅仅读取一个 C 文件可能是一个很大的负担。最小化内存占用非常重要。因此,几乎不需要加载的抽象非常有用。今天,它在执行网络通信时同样有用,而且事实证明,在我们处理文件时,它很少受到限制。以一般方式透明地添加缓冲等内容的能力使其更加有用。
【讨论】:
流是对字节序列的抽象。这个想法是你不需要知道字节来自哪里,你可以以标准化的方式读取它们。
例如,如果您通过流处理数据,那么如果数据来自文件、网络连接、字符串、数据库中的 blob 等等等,则对您的代码无关紧要。
与后备存储本身进行交互本身并没有错,只是它将您与后备存储实现联系在一起。
【讨论】:
流是一种抽象,它提供一组标准的方法和属性来与数据交互。通过从实际存储介质中抽象出来,您的代码可以在不完全依赖该介质是什么甚至该介质的实现的情况下编写。
一个很好的类比可能是考虑一个包。你不在乎一个包是用什么做的,或者当你把你的东西放进去时它会做什么,只要这个包能起到一个包的作用,你可以把你的东西拿出来。流为存储媒体定义了袋子的概念为袋子的不同实例(例如垃圾袋、手提袋、背包等)定义的内容——交互规则。
【讨论】:
我会保持简短,我只是在这里漏掉了这个词:
流是队列,通常存储在包含任何类型数据的缓冲区中。
(现在,既然我们都知道什么是队列,那就不用再解释了。)
【讨论】: