在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. UVA 11609 Teams 组合数学+快速幂

    In a galaxy far far away there is an ancient game played among the planets. The specialty of the gam ...

  2. AngularJS学习笔记2——AngularJS的初始化

    本文主要介绍AngularJS的自动初始化以及在必要的适合如何手动初始化. Angular <script> Tag 下面通过一小段代码来介绍推荐的自动初始化过程: <!doctyp ...

  3. Android 核心分析之十三Android GWES之Android窗口管理

    Android GWES之Android窗口管理1基本构架原理 Android的窗口管理是C/S模式的.Android中的Window是表示Top Level等顶级窗口的概念.DecorView是Wi ...

  4. linux shell 命令学习(2) paste - merge lines of files

    paste - merge lines of files 命令格式: paste [OPTION] ... [FILE] ... 说明: 输出每个文件的对应行组合而成的行,中间用tab分开,如果没有F ...

  5. Android开发常用代码片段

    拨打电话 public static void call(Context context, String phoneNumber) { context.startActivity( new Inten ...

  6. Android TabHost中Activity之间传递数据

    例子1: TabHost tabhost = (TabHost) findViewById(android.R.id.tabhost); tabhost.setup(this.getLocalActi ...

  7. highcharts联合jquery ajax 后端取数据

    Highcharts是一个制作图表的纯Javascript类库,主要特性如下: 兼容性:兼容当今所有的浏览器,包括iPhone.IE和火狐等等: 对个人用户完全免费: 纯JS,无BS: 支持大部分的图 ...

  8. c# 计算1-100之间的所有奇数的和

    求1-100之间所有奇数整数和: class Program { static void Main(string[] args) { ,); Console.WriteLine("1-100 ...

  9. 截取usb数据包,控制usb设备----Relay设备

    在项目开发当中,我们需要一个usb转继电器的设备当开关控制无线发射设备,采购部采购时并未详细了解Relay设备的运行环境就买了一批设备,之后发现设备厂家只提供了windows库,而我们是要在linux ...

  10. 《c程序设计语言》读书笔记--字符串比较

    举例如下: char a[10]; 1.定义的时候直接用字符串赋值 char a[10]="hello"; 注意:不能先定义再给它赋值,如  char a[10];  a[10]= ...