I/O和文件

输入/输出(I/O)就是在内存和外部设备之间复制数据的过程。输入(input)就是从I/O设备复制数据到内存,输出(output)就是从内存复制数据到I/O设备。

一个文件可以理解成一串字节序列。所有的I/O设备,如网络、磁盘和终端,都被抽象为文件。所有的输入和输出都可以简化地抽象为对相应文件的读或写。对文件的操作包括:

  • 打开文件。一个应用程序可以请求内核”打开“一个文件(内核再请求硬件去访问某个I/O设备),内核会返回一个叫做描述符(descriptor)的非负整数,用来标识此文件。应用程序可以拿着这个描述符对此文件继续做其他操作。
  • 改变当前的文件位置。内核会记录每一个打开的文件的“文件位置” k,这个k指向字节序列中的某个字节,每次读写都会相应地改变k的值,初始为文件的开头(k=0)。应用程序也可以使用seek函数来显示设置当前文件位置。
  • 读写文件。读就是从文件中复制n个字节到内存,从当前的文件位置k开始,然后将k设为k + n。当读到文件的末尾时,会触发end-of-file (EOF)。类似地,写就是从内存中复制n个字节到文件,也是当前的文件位置k开始,然后将k设为k + n。
  • 关闭文件。当应用程序不再需要一个文件时,它会请求内核关闭这个文件,然后内核会释放所有与这个文件相关的数据结构对象(打开文件时创建的数据结构对象)。当一个应用程序终止时,内核会关闭所有它打开的文件。

目录和缓冲区

文件分为普通文件和目录( directory),以及其他类型,如socket。一个目录就是一个包含了一组链接(link)的文件,每个链接都是一个文件名,这个文件名指向一个文件(当然可能也是一个目录)。每个目录都包含...这两个链接,其中.指向这个目录本身,..指向这个目录的父目录。每个进程的上下文中都保存着一个叫做当前工作目录(current working directory)的状态。相对路径就是从当前工作目录开始,到某个文件的路径。

假设我们需要从一个ASCII文本文件中一行一行地读取,该如何做?一种办法是一次读取一个字节,然后判断一下这个字节是不是换行符。这种办法的缺点是效率不是很高,因为每次读取一个字节都需要切换到内核模式。一种更好的办法是先将一定数量的字节从文件中读到一个内部的缓冲区(buffer)中,然后再从这个缓冲区中一个字节一个字节地读取,当这个缓冲区被读完时,就自动再从文件中读一些字节过来。

Stream

高级语言中的I/O操作的API往往包含stream的概念。stream可以理解为对文件的抽象,但内部往往使用了缓冲区(作用同样是为了减少切换到内核模式的开销)。stream封装了操作系统提供的I/O模型,也就是我们不需要关心内核级别的函数,只需要对一个stream进行操作即可。类似文件,我们可以对一个Stream进行的操作包括读或写等等。

对于操作系统来说,EOF(end-of-file)是一个会被内核检测到的状态,对于磁盘文件,EOF会在当前文件位置超过此文件长度时发生,对于一次网络连接,EOF会在另一端关闭连接时发生。而Stream是对文件的高层抽象,也可以有自己的“EOF”(其实称作end of stream更合适)。以C#为例,StreamReader.ReadToEnd会一直读取Stream直到遇到EOF才返回,若stream是一个socket,则直到另一端的socket关闭了这次连接之前,StreamReader.ReadToEnd都不会返回,因为只有另一端关闭连接才会触发这一端的EOF。

var tcpClient = new TcpClient(host, port);
var connectionStream = tcpClient.GetStream();
using (StreamReader reader = new StreamReader(connectionStream))
{
await reader.ReadToEndAsync(); // 直到另一端关闭连接之前,会一直“阻塞”在这里
}

但是下面的代码,即使是有Connection: Keep-Alive的HTTP持久连接,也不会有问题:

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
HttpWebResponse response = (HttpWebResponse)(await request.GetResponseAsync());
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
await reader.ReadToEndAsync(); // 即使另一边还没有关闭这次连接,也不会“阻塞”在这里
}

原因在于HttpWebResponse.GetResponseStream返回的并不是NetworkStream,而是一个特殊的Stream,叫作ConnectStream,专门用于读取一次HTTP响应的body。我们知道,HTTP是建立于TCP之上的,在一次TCP连接中,任何一端都可以对其socket无限制地读或写,理论上客户端可以无限地从socket中读取或等待读取数据,所以HTTP才会有Content-Length,来告诉客户端这一次响应有多大,该读取多少数据就够了。对于这个ConnectStream来说,当从底层的NetworkStream中读取的字节数达到指定的Content-Length大小时,就算是EOF。

所以,Stream相当于提供了一个抽象接口,不同的实现有不同的行为,但对外提供的功能是一致的。另外,Stream的读操作和操作系统提供的函数很类似,都会返回本次读取了多少字节,每次真正读取的字节数不一定等于输入参数中要求读取的字节数(特别是在网络编程时),有时候很容易忽视这一点。

参考

《深入理解计算机系统》第三版 第十章 System-Level I/O

文件和Stream的更多相关文章

  1. C# 流与文件(Stream & File & byte[])

    原文:https://www.cnblogs.com/long-gengyun/archive/2010/03/28/1698681.html 文件概述  文件在操作时表现为流,即流是从一些输入中读取 ...

  2. 通过文件流stream下载文件

    public ActionResult ShowLocalizedXML(int id) { string orderName = ""; string xmlString = G ...

  3. .net c# 提交包含文件file 的form表单 获得文件的Stream流

    1.前台html代码 要写一个有id的form,可是不能有runat="server"属性.由于一个页面中,有这个属性的form表单仅仅能有一个. 再要有一个有name的ifram ...

  4. C# 将文件转换为 Stream

    public Stream FileToStream(string fileName) { // 打开文件 FileStream fileStream = new FileStream(fileNam ...

  5. Java中的文件和stream流的操作代码

    1.Java中FileRead方法的运用代码及详解 package example2;import java.io.FileReader;import java.io.IOException;clas ...

  6. linux下载网页上的文件夹以及删除文件(stream)

    wget -nd -r -l1 --no-parent http://www.cs.virginia.edu/stream/FTP/Code/ 注:-nd 不创建目录:-r 递归下载:-l1只下载当前 ...

  7. 14、Java文件操作stream、File、IO

    1.文件操作涉及到的基本概念 File File类 是文件操作的主要对象中文意义就是 文件 顾名思意 万物皆文件,在计算上看到的所有东西都是文件保存,不管是你的图片.视频.数据库数据等等都是按照基本的 ...

  8. C#下载文件,Stream 和 byte[] 之间的转换

    stream byte 等各类转换 http://www.cnblogs.com/warioland/archive/2012/03/06/2381355.html using (System.Net ...

  9. C#:文件、byte[]、Stream相互转换

    一.byte[] 和 Stream /// <summary> /// byte[]转换成Stream /// </summary> /// <param name=&q ...

随机推荐

  1. try catch和finally

    在C#中这三个关键字用于处理异常. 这三个关键字try是必定要用的,要不然就失去了意义.然后catch和finally可以不用但是要注意遵循原则. 存在一个或多个catch的时可以不用finally, ...

  2. 08 nginx+uWSGI+django+virtualenv+supervisor发布web服务器

    一.为什么要用nginx,uwsgi? 1 首先nginx 是对外的服务接口,外部浏览器通过url访问nginx, 2nginx 接收到浏览器发送过来的http请求,将包进行解析,分析url,如果是静 ...

  3. mock.js 模拟数据

    1. 劫持请求,返回模拟数据: 用于前后台对接前数据模拟 相比于静态json文件而言:代码完成后不必修改源文件对应的接口调用.可模拟增删改查 2.实例代码 <!doctype html> ...

  4. Java操作FTP,从FTP上读取指定文件,把指定文件上传到FTP

    需要添加的依赖 <!-- https://mvnrepository.com/artifact/commons-net/commons-net --> <dependency> ...

  5. FastDFS整合Nginx的模块:fastdfs-nginx-module报错:fdfs_define.h:15:27: 致命错误:common_define.h:没有那个文件或目录

    错误提示: /usr/include/fastdfs/fdfs_define.h:15:27: 致命错误:common_define.h:没有那个文件或目录 [解决办法] 1.编辑fastdfs-ng ...

  6. paramiko:实现ssh协议,对linux服务器资源的访问

    介绍 网络传输是遵循协议的,比如SSH,paramiko则是实现了SSHv2协议的一个python库(底层使用的是cryptography).有了paramiko之后,我们便可以通过python使用s ...

  7. 测开常见面试题什么是redis

    企业中redis是必备的性能优化中间件,也是常见面试题,首先Redis是由意大利人Salvatore Sanfilippo(网名:antirez)开发的一款内存高速缓存数据库.Redis全称为:Rem ...

  8. 关于order_by

  9. vim简明教程(附快速记忆方法)

    vim分为四种模式: 普通模式(normal mode) 插入模式(insert mode) 可视模式(visual mode) 命令模式(excute mode) 下面整理了常用的快捷键和记忆方法( ...

  10. Lambda学习总结(二)--Stream流

    一.Stream 流 1.1 概念 官方解释:可以支持顺序和并行对元素操作的元素集合. 简单来讲,Stream 就是 JDK8 提供给我们的对于元素集合统一.快速.并行操作的一种方式. 它能充分运用多 ...