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. 第二章 单表查询 T-SQL语言基础(1)

    单表查询(1) 本章:逻辑查询处理,特定的SELECT查询生成正确的结果集而要经历的一系列逻辑阶段;单表查询的其他方面,包括:运算符,空值(NULL),字符的处理和临时数据,分级(ranking),C ...

  2. FTP服务器上传,下载文件

    public class FtpUtil { /** * * @param host FTP服务器地址 * @param port FTP服务器端口 * @param username FTP登录账号 ...

  3. 04 定时任务及yum源的选择

    1.查看系统的发行版本 cat /etc/redhat -release cat /etc/os -release 2.用户管理 linux超级用户 root拥有最高权限 管理员 sudo命令就是ro ...

  4. 10 Scrapy框架持久化存储

    一.基于终端指令的持久化存储 保证parse方法中有可迭代类型对象(通常为列表or字典)的返回,该返回值可以通过终端指令的形式写入指定格式的文件中进行持久化操作. 执行输出指定格式进行存储:将爬取到的 ...

  5. int 问号的使用

    单问号---为泛型 Nullable<int> 的简写方式. 双问号---用于判断前一个操作数是否为null,如为null则"返回"后一个操作数,否则"返回& ...

  6. TVM:一个端到端的用于开发深度学习负载以适应多种硬件平台的IR栈

    TVM:一个端到端的用于开发深度学习负载以适应多种硬件平台的IR栈  本文对TVM的论文进行了翻译整理 深度学习如今无处不在且必不可少.这次创新部分得益于可扩展的深度学习系统,比如 TensorFlo ...

  7. json串到java对象

    json串到java对象 前端传入参数json字符串,格式如下: {"语文":"88","数学":"78"," ...

  8. Linux: df du

    df :列出文件系统的整体磁盘使用量 du :评估文件系统的磁盘使用量(常用在推估目录所占容量) 1 df  :[-ahikHTm] 目录或文件名 选项或参数: -a:  列出所有的文件系统,包括系统 ...

  9. 牛客练习赛47 E DongDong数颜色 (树状数组维护区间元素种类数)

    链接:https://ac.nowcoder.com/acm/contest/904/E 来源:牛客网 DongDong数颜色 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 5242 ...

  10. java8学习之Collector复合与注意事项

    接着上一次[http://www.cnblogs.com/webor2006/p/8318066.html]继续对Collector进行javadoc详读,上一次读到了这: 接下来一条条来过目一下: ...