流(stream)是怎么一回事
—— 对这个问题的思考来源于前几天对 Java Socket 编程的尝试,TCP 协议要求建立一个 Socket 连接(著名的三次握手)之后才能进行通信,而连接双方进行数据的发送与接受,都是通过对输入输出流的机制来完成的。
流的概念
流作为概念应该是语言无关的。文件IO流,Unix系统标准输入输出流,标准错误流(stdin, stdout, stderr),还有一开始提到的 TCP 流,还有一些 Web 后台技术(如Nodejs)对HTTP请求/响应流的抽象,都可以见到流的概念。
K&R 在 C Programming Language 书中提到流是这样定义的:
流 (stream) 是与磁盘或其它外围设备关联的数据的源或目的地。
可以把流理解成是对程序与外界交换数据的一种抽象,这里的外界限定是有必要的,通常不会把程序内部的数据流动抽象为流,毕竟在程序内部,数据流动是由函数调用、返回来完成的。而当我们使用三个标准IO流时,我们关心的是怎样通过它们与外界交互;当我们使用文件流时,我们关心的是将内存中的数据持久化到磁盘文件中(或从磁盘中读数据导内存)。
于是数据从 A 处“流”向 B 处,可以类比像水流一样从高处流向低处。在水流动的过程中,作为最基本物理组成单位的水分子是不变的,相应的数据流也有它最小的组成单位。在不同的编程语言中,这个最小单位通常是字节流(二进制流)中的字节,或者字符流(文本流)中的字符。
——但不会是其他数据类型,就像我们从来没听说过数字流?,或者浮点数流,甚至数组流?
因为字节是计算机保存数据的最终形式,而字符是其它数据结构序列化后的表现形式,也是人可以阅读的形式。与外界的交互需要这些通用的格式。不关心数据的内容,只需要完整地传输原始数据时,考虑字节流即可;关心传输字符和字符串时,就需要对字符流进行操作,stdio.h
头文件里那一大坨输入输出函数就是干这个的。比如fgetc(FILE *stream)
从文本流中读入一个字符。
另一方面,根据数据流动的方向,可以再抽象出输出流和输入流的概念。从程序内部到外部的流向是输出流,从程序外部到内部的流向是输入流。
C 语言的stdio.h
库中定义了打开文件流时必须指定的集中打开方式,"r"
表示用于读取,"w"
用于写入,"r+"
用于读写。类似地,Java 语言的java.io
包中包含了InputStream
, OutputStream
明确区分的输入流类和输出流类,并且二者都是抽象类,意味着必须根据需要使用它们各自的子类进行实例化。
通过流操作实现(最)简单的文件拷贝
根据实际的代码可以帮助理解stream,下面是一段用C语言标准库实现的最简单的文件拷贝功能。
出于学习目的,这段代码偷懒没有任何容错功能,是典型的反面教材, 不过 whatever 了,不信你真拿去编译一下,是真的可以完整拷贝文件!除了不能拷贝目录,不能拷贝不存在的文件,不能拷贝文件权限,不能漏掉目的文件名或者路径,不能灵活处理文件软链接硬链接。等等等等blahblah(所以其实连看上去很简单的cp
程序也是要有一大坨因素要考虑和支持的(啊跑题了
// mini_cp.c
#include <stdio.h>
#define BUFFER_SIZE 512int main(int argc, char *argv[])
{ // 从命令行参数中获得 SOURCE 和 DES 文件流FILE *src = fopen(argv[1], "rb");FILE *des = fopen(argv[2], "wb");long int num;// buffer 是读写的缓冲数组char buffer[BUFFER_SIZE];while(!feof(src)) {num = fread(buffer, sizeof(char), BUFFER_SIZE, src);fwrite(buffer, sizeof(char), num, des);}fclose(src);fclose(des);return 0;
}
这个自制的mini_cp
程序不难理解,核心的逻辑可以分解为三个步骤:
打开源文件流
FILE *src
和目的文件流FILE *des
循环执行 { 每次从
src
流读取最多512字节的数据 => 并写入des
流 } 直到源文件读取结束关闭文件流
核心逻辑是非常清晰明了的,这样的逻辑也是流操作的普遍原理,尝试其他语言的实现,其实都已经大同小异,往往都少不了一个缓冲区
的概念(或对象)。
来看一下 Java 版本的同等实现:
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;public class Copy {private static final int BUFFER_SIZE = 512;public static void main(String[] args) throws IOException {File srcFile = new File(args[0]);File desFile = new File(args[1]);int recvBytesSize;byte[] buffer = new byte[BUFFER_SIZE];FileInputStream in = new FileInputStream(srcFile);FileOutputStream out = new FileOutputStream(desFile);while((recvBytesSize = in.read(buffer)) != -1) {out.write(buffer, 0, recvBytesSize);}in.close();out.close();}
}
面向对象味更浓(代码更冗长)了有木有?但也正是因为面向对象,Java 把理论上的 stream 抽象为类,让我们直接获得类的实例(即对象),从而对对象进行操作。还是挺不赖的是吧,虽然代码更长了没错,但是更 OO 啊~
写到这里已经能回答流基本是怎么一回事了,那么最后顺便再来放一段拷贝程序的ruby实现;
require 'fileutils'
FileUtils.cp('SOURCE.txt', 'DEST.txt')
哈?
嗯。
... That's why we love Ruby...(逃。。。
流(stream)是怎么一回事相关推荐
- C语言ftell()函数(返回文件当前位置)(返回给定流 stream 的当前文件位置)
C 标准库 - <stdio.h> 描述 C 库函数 long int ftell(FILE *stream) 返回给定流 stream 的当前文件位置. 声明 下面是 ftell() 函 ...
- c语言fgetc()函数(从指定的流 stream 获取下一个字符(一个无符号字符),并把位置标识符往前移动)
C 标准库 - <stdio.h> 文章目录 描述 声明 参数 返回值 实例 描述 C 库函数 int fgetc(FILE *stream) 从指定的流 stream 获取下一个字符(一 ...
- C语言fputs()函数(把字符串写入到指定的流 stream (文件指针)中)
C 标准库 - <stdio.h> 注意:fgets和fputs是字符串读写,fread和fwrite是数据块读写 文章目录 描述 声明 参数 返回值 实例 描述 C 库函数 int fp ...
- C语言函数fscanf()(从流 stream 读取格式化输入)(如果成功,返回成功匹配和赋值的个数;否则返回EOF)(分隔符:space、Tab、Enter)
C 标准库 - <stdio.h> 文章目录 描述 声明 参数 返回值 实例1 实例2 实例3 注意(分隔符:space.Tab.Enter) 换行格式化输入示例(暂时只能弄英文字符,中文 ...
- C语言rewind()函数(设置文件位置为给定流 stream 的文件的开头)(回到文件开头重读)
C 标准库 - <stdio.h> 文章目录 描述 声明 参数 返回值 实例 描述 C 库函数 void rewind(FILE *stream) 设置文件位置为给定流 stream 的文 ...
- Java 方法、 流(Stream)、文件(File)和IO 总结
这里只总结几个要点. 1. 方法的命名规则 1.方法的名字的第一个单词应以小写字母作为开头,后面的单词则用大写字母开头写,不使用连接符.例如:addPerson. 2.下划线可能出现在 JUnit 测 ...
- redis 流 stream的使用总结 - 消费者组
本博客讲述如何使用redis中流stream的组 简言 1. 消费者组(consumer group)允许用户将一个流从逻辑上分成多个不同的流,并让消费者组组下的消费者去处理组中的消息 2. 多个消费 ...
- redis 流 stream的使用总结 - 如何遍历
本博客讲述如何对redis中的流进行遍历 接上篇博客redis 流 stream的使用总结 - 基础命令 简言 1. XRANGE,XREVRANGE,XREAD命令只适合单个消费者模式,因为这三个命 ...
- redis 流 stream的使用总结 - 基础命令
简言 1. 流(stream)是redist5.0版本新增加的数据结构,也是该版本最重要的更新,专门用于实现消息队列,事件系统 2. redis之前的其他的数据结构实现消息队列,各有缺点 2. ...
最新文章
- 1行Python代码制作动态二维码
- Docker(十四):Docker:网络模式详解
- display:table和display:table-cell的妙用
- 【高性能定时器】时间堆(最小堆)
- [译]技术之外,工作之内,非常实际有用的技巧--如何宣布坏消息?
- java: Comparable比较器,数组对象比较器
- 一次docker中的nginx进程响应慢问题定位记录
- Android图片,PNG还是JPG?用哪种?
- 计算机管理说文件缺失lnk,Win10系统右键管理打不开提示Windows找不到文件Server manager.lnk如何解决...
- Java学习路线|【完整版】
- 清除html宏病毒,表格宏病毒怎么查杀 Excel宏病毒怎么清除?
- jsp定义java方法_jsp中java成员变量、方法的声明以及使用
- Research Mindmap
- 手机修图软件测试,10款好用的手机图片编辑器软件排行榜
- Java定义获取所有偶数元素集合的方法
- 数学建模常用模型05 :多元回归模型
- ubuntu安装java_如何在Ubuntu系统上安装Java
- iOS及Android自动化实践
- USACO 2020 December Contest, BronzeProblem 2. Daisy Chains题解
- 远距离无线音视频传输方案,物联网技术应用,无线远距离WiFi通信技术
热门文章
- Vue基本使用---vue工作笔记0002
- JAVA面试要点009---TimeUnit用法
- Android学习笔记07---查看Android虚拟机输出的错误信息与如何部署应用到自己的真实手机
- python越来越慢_为什么我的算法越来越慢?
- 【单片机基础篇】舵机模块使用
- 3d激光雷达开发(生成RangeImage)
- 一步一步写算法(之图创建)
- c语言divide error,Python numpy.corrcoef()RuntimeWarning:在true_divide中遇到无效值c / = stddev [:,None]...
- java xml binding_JAXB(Java Architecture for XML Binding)
- java面试请你谈谈mysql_Java面试题之MySQL