在unix世界中视一切为文件,无论最基本的文本文件还是网络设备或是u盘,在内核看来它们的本质都是一样的。大多数文件IO操作只需要用到5个函数:openreadwritelseek 以及 close. 这些函数并不是 ISO C 的组成部分,而是unix的系统调用函数。相比较ISO C中带缓冲的IO操作函数,它们是不带缓冲的IO函数。它们之间的区别看上去是这样的:

可以简单的这么来理解,write函数直接将内容写到文件中。而像printf这样的函数将内容先写到缓冲区,然后由缓冲区的方式(行缓冲或者全缓冲)来决定什么时候将缓冲区的内容写到具体的文件中。下面对文件IO操作中的概念还有函数一一介绍。

文件IO的基本概念

下面的这张图表示了打开文件的内核数据结构:

简单来说,首先是一个文件面向用户的文件描述符,然后文件指针指向一个文件表,用来保存文件相关的信息。接下来可以认为与文件物理存储相关的结构。其中涉及的几个文件IO基本概念解释如下:

文件描述符

当调用open函数打开一个文件时会返回一个非负整数来标志该文件。一方面方便内核的管理,另一方面方便代码基于此标志进行后续的文件操作。在UNIX系统中,stdin与文件描述0相互关联;stdout与文件描述符1相互关联;stderr与文件描述符2相互关联。并使用下面的宏定义来提高程序的可读性:STDIN_FILENOSTDOUT_FILENOSTDERR_FILENO.

文件描述符标志

这个概念容易混淆,文件描述符标志即close-on-exec标志位。如果文件描述符设置了该标志,表示在通过fork函数产生的子进程中调用exec族函数时,该文件描述符会被自动关闭。这样做可以用来保护文件描述符资源,防止泄露。

文件状态标志

文件状态标识用来说明该文件的基本属性。比如是否可读、是否可写、是否阻塞等等。下一小节中会详细的描述。

文件偏移量

当执行读或者写操作时,第一个面临的问题就是在什么位置执行这些操作。文件偏移量这个标志就指明了当前文件相对于起始位置的偏移量。

open函数

open函数的原型如下:

#include <fcntl.h>           

int open(const char *path, int oflag, .../* mode_t mode */);

                            返回值:若成功返回文件描述符,若出错返回-1

此函数用 path 参数指定文件的路径,用 oflag 参数指定文件状态标识,说明如下:

O_RDONLY: 只读打开

O_WRONLY: 只写打开

O_RDWR: 读写打开

以上三种属于互斥的关系,只能选其一。下面的这些标志位可以通过 '|' 操作来进行叠加。

O_APPEND: 每次写时都追加到文件的尾端。

O_CLOEXEC: 它的含义在上文 文件描述法标志位 中已经阐述清楚了。

O_CREAT: 若文件不存在则创建它。使用此选项时,open函数需同时说明第三个参数mode,来指定该文件的访问权限。如果文件已经存在并不会出错,而是重新创建新文件。

O_EXCL: 如果同时指定了OCREATE,且文件存在,则出错。这个标志可以用来测试文件是否存在,也可以保证对现有文件的保护。

O_NOCTTY: 如果path引用的是终端设备,则不将该设备分配作为此进程的控制终端

O_NOBLOCK: 如果path引用的是一个FITO、一个块特殊文件或一个字符特殊文件,则此选项为文件的本次打开操作和后续的IO操作设置非阻塞方式。

O_SYNC: 使每次write等待物理IO操作完成,包括由该write操作引起的文件属性更新所需的IO。

O_TRUNC: 如果文件存在,而且为只写或读-写成功打开,则将其长度截断为0。

常用的标志位为 O_APPEND, O_CLOEXEC, O_NOBLOCK, O_TRUNC。这些标志位可以通过使用 | 操作符来实现多个开启,如

open("./test.txt", O_RDWD | O_APPEND | O_NOBLOCK);

以读写、追加、非阻塞的方式打开当前目录下的test.txt文件。

此外,openat()create() 函数也可以实现打开文件的功能。但一般使用 open 函数即可。

lseek函数

lseek用于设置当前文件偏移量,其原型如下:

#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);

                          返回值:若成功返回文件的新的偏移量,若出错返回-1

这个函数将fd描述符的文件的偏移量设置到距离 whence 位置 offset 字节数的地方。whence 示意如下:

SEEK_SET: 文件开始处

SEEK_CUR: 文件当前位置

SEEK_END: 文件结尾

lseek 成功执行,返回新的文件偏移量。这是一个非常有用的函数。如果想知道文件当前的偏移量,可以使用下面的这个技巧。

off_t offset;
offset = lseek(fd, , SEEK_CUR);

read write函数

读写函数放在一起比较,便于记忆。read 函数的原型如下:

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t nbytes);

                      返回值:若成功返回读到的字节数,若已到文件结尾返回0,若出错返回-1

fd为所读取文件的描述符,buf用于存放的缓冲区指针,nbytes为预计要读取的字节数。关于返回值:如果read执行成功,返回实际读到的数据(如已到文件尾端,返回0);如果read出错,返回-1。

有多种的情况下,read 函数多读取的字节数可能会小于所要求的字节数,在网络编程下尤其要注意这一点。

write的函数原型如下:

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t nbytes);

                        返回值:若成功则返回已写的字节数,若出错返回-1

buf指针指向缓存区,nbytes为预计要写的字节数,若 write 函数执行成功,返回实际写入的字节数;若出错,返回-1。其返回值通常与参数nbytes的值相同,否则表示出错。

文件IO操作举例

下面举一个文件处理的小例子,原来文件的结构如下:

[task]
1. test 1
2. test 2
[End]

写一个函数来实现向task和end之间插入一个新的项。下面是代码实现:
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

void taskAdd(const char *str)
{
    int fd;
    ;
    int i;
    char c;
    off_t offset;
    ] = {};
    ] = {};

    // copy data
    strcpy(line, str);
    int len = strlen(line);
    line[len] = '\n';
    line[len + ] = '\n';

    // open the file
    )
    {
        printf("open error\n");
        return;
    }

    // read one line once a time
    )) >  && nRead < )
    {
       )) == '\n')
       {
           // find the [End] flag
           )
           {
               , SEEK_CUR)) < )
               {
                    printf("lseek error\n");
                    return;
               }

               ) < )
               {
                    printf("read error\n");
                    return;
               }
               , SEEK_SET)) < )
               {
                    printf("lseek error\n");
                    return;
               }

               )
               {
                    printf("write error\n");
                    return;
               }

               )
               {
                    printf("write error\n");
                    return;
               }
               close(fd);
               return;
           }

           memset(buf, , );
           nRead = ;
       }
    }
}

文件IO的更多相关文章

  1. 标准io与文件io

    A: 代码重复: 语句块1: while(判断) { 语句块2: 语句块1: } 上面可以改写为: while(1) { 语句块1: if(判断) break: 语句块2: } B: 标准IO和文件I ...

  2. 文件IO函数和标准IO库的区别

    摘自 http://blog.chinaunix.net/uid-26565142-id-3051729.html 1,文件IO函数,在Unix中,有如下5个:open,read,write,lsee ...

  3. 转 漫谈linux文件IO

    在Linux 开发中,有几个关系到性能的东西,技术人员非常关注:进程,CPU,MEM,网络IO,磁盘IO.本篇文件打算详细全面,深入浅出.剖析文件IO的细节.从多个角度探索如何提高IO性能.本文尽量用 ...

  4. (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

  5. Java文件IO操作应该抛弃File拥抱Paths和Files

    Java7中文件IO发生了很大的变化,专门引入了很多新的类: import java.nio.file.DirectoryStream;import java.nio.file.FileSystem; ...

  6. Java 文件IO续

    文件IO续 File类    用来将文件和文件夹封装成对象 方便对文件和文件夹的属性信息进行操作    File对象可以作为参数传递给流的构造函数 Demo1 File的构造方法 public cla ...

  7. Java 文件IO

    文件IO Java IO    IO流用来处理设备之间的数据传输 Java对数据的操作是通过流的方式 Java用于操作流的对象都在IO包中    按操作数据分为 字节流和字符流        字符流的 ...

  8. 文件IO和标准IO

    2015.2.26 星期四,阴天 今天的内容主要是文件IO man 手册的分册: man -f open 查看那些分册中有openman 1 -- 普通的命令程序man 2 -- 系统调用man 3 ...

  9. 文件IO操作

    前言 本文介绍使用java进行简单的文件IO操作. 操作步骤 - 读文件 1. 定义一个Scanner对象 2. 调用该对象的input函数族进行文件读取 (参见下面代码) 3. 关闭输入流 说明:其 ...

随机推荐

  1. POJ 2001

    #include<iostream> using namespace std; ; struct trienode { trienode * next[kind]; int branch; ...

  2. 优雅的python 写排序算法

    arr=[] while True: #输入数据 当输入q结束 a=raw_input() if a=="q": break arr.append(int(a)) s=len(ar ...

  3. Linux网络编程6——使用TCP实现文件服务器

    需求 当客户端连接上服务器后,服务器会将相应文件传输给客户端,实现文件下载. 思路 服务器端,主进程负责listen.循环内,主进程每从任务请求队列中accept出一个请求,就fork出孙子完成文件传 ...

  4. C Primer Plus之结构和其他数据形式

    声明和初始化结构指针 声明结构化指针,例如: struct guy * him; 初始化结构指针(如果barney是一个guy类型的结构),例如: him = &barney; 注意:和数组不 ...

  5. 关于null == 0?返回false的问题

    1.首先我们先看各种情况的结果: null > 0? //=>false null < 0? //=>false null >= 0? //=>true null ...

  6. 第一个React程序HelloWorld

    一.程序步骤 1.用React.createClass生成组件 2.调用React.render把组件渲染到页面中,dom的操作由react自动完成 二.代码 <!DOCTYPE html> ...

  7. WaitForSingleObject与WaitForMultipleObjects用法详解(好用,而且进入一个非常高效沉睡状态,只占用极少的CPU时间片)

    在多线程下面,有时候会希望等待某一线程完成了再继续做其他事情,要实现这个目的,可以使用Windows API函数WaitForSingleObject,或者WaitForMultipleObjects ...

  8. 我们为什么需要DTO(数据传输对象)

    原文:http://www.cnblogs.com/Gyoung/archive/2013/03/23/2977233.html DTO即数据传输对象(Data Transfer Object).之前 ...

  9. Docker基础技术:Linux Namespace(上)

    时下最热的技术莫过于Docker了,很多人都觉得Docker是个新技术,其实不然,Docker除了其编程语言用go比较新外,其实它还真不是个新东西,也就是个新瓶装旧酒的东西,所谓的The New “O ...

  10. openfire插件开发1

    http://www.cnblogs.com/hoojo/archive/2013/03/29/openfire_plugin_chatlogs_plugin_.html http://www.cnb ...