系统 I/O

输入输出(I/O)是主存和外部设备设备(磁盘驱动器、终端、网络等)之间复制数据的过程。

Unix I/O 是系统底层数据操作

img

open() 和 close() 来打开和关闭文件,使用 read() 和 write() 来读写文件,或者利用 lseek() 来设定读取的偏移量

  • 文件类型:
  1. 普通文件:包含任意数据
  2. 目录:相关一组文件的索引
  3. 套接字 Socket:和另一台机器上的进程通信的类型

目录包含一个链接 (link) 数组,并且每个目录至少包含两条记录:
./ 当前目录
../ 上一层目录

相对路径和绝对路径

  • 打开文件

    open 函数(返回的文件描述符一定是最小的且没有被用过的数值)

int open(const char *pathname, int flags, mode_t mode)
// flags 用于指定文件的打开 / 创建模式, 第三个参数仅当创建新文件时才使用,用于指定文件的访问权限位(access permission bits)

flags:
O_RDONLY 只读模式
O_WRONLY 只写模式
O_RDWR 读写模式
. . .

modes:
S_IRWXU00700 权限,代表该文件所有者具有可读、可写及可执行的权限。
S_IRUSR 或 S_IREAD,00400 权限,代表该文件所有者具有可读取的权限。
S_IWUSR 或 S_IWRITE,00200 权限,代表该文件所有者具有可写入的权限。
S_IXUSR 或 S_IEXEC,00100 权限,代表该文件所有者具有可执行的权限
. . .

int fd; // 文件描述符
if ((fd = open("/etc/hosts", O_RDONLY)) < 0) // 返回值等于 -1 则说明发生了错误
{
    perror("open");
    exit(1);
}
  • 关闭文件
    close 函数用于关闭已打开的文件, 关闭一个已关闭的描述符会出错
int close(int fd)
int fd;     // 文件描述符
int retval; // 返回值
if ((retval = close(fd)) < 0)
{
    perror("close");
    exit(1);
}
  • 读取文件
    实际上就是把文件中对应的字节复制到内存中
ssize_t read(int fd, void *buf, size_t count) // 成功执行时,返回所读取的数据量,如果读到文件的末尾 EOF 则返回 0。失败的时候返回 - 1

buf: 所要读取到的数据的内存缓冲
count:需要读取的数据量

char buf[512];
int fd;
int nbytes;
// 打开文件描述符,并从中读取 512 字节的数据
if ((nbytes = read(fd, buf, sizeof(buf))) < 0)
{
    perror("read");
    exit(1);
}
  • 写入文件
ssize_t write(int fd, const void *buf, size_t nbytes) // write 函数将 buf 中的 nbytes 字节内容写入文件描述符 fd. 成功时返回写的字节数. 失败时返回 - 1
char buf[512];
int fd;
int nbytes;
// 打开文件描述符,并向其写入 512 字节的数据
if ((nbytes = write(fd, buf, sizeof(buf)) < 0)
{
    perror("write");
    exit(1);
}

tips:x86-64 系统中,size-t 被定义为 unsigned long,而 ssize-t(有符号)被定义为 long,因为函数返回值可能为 - 1

  • lseek 函数
    为一个打开的文件设置其偏移量
off_t lseek(int fd, off_t offset, int whence)

Offset:偏移量,每一读写操作所需要移动的距离,单位是字节的数量,可正可负(向前,向后)

whence: SEEK_SET / SEEK_CUR / SEEK_END (依次为 0,1,2).

SEEK_SET 将读写位置指向文件头后再增加 offset 个位移量。

SEEK_CUR 以目前的读写位置往后增加 offset 个位移量。

SEEK_END 将读写位置指向文件尾后再增加 offset 个位移量。

当 whence 值为 SEEK_CUR 或 SEEK_END 时,参数 offet 允许负值的出现

当调用成功时则返回目前的读写位置,也就是距离文件开头多少个字节。若有错误则返回 - 1,errno 会存放错误代码

  • 元数据
    元数据是用来描述数据的结构,由内核维护,可以通过 stat 和 fstat 函数来访问
int stat(const char *filename, struct stat *buf)
int fstat(int fd, struct stat *buf)

函数结果是填充一个 stat 结构:

struct stat
{
    dev_t           st_dev;     // Device
    ino_t           st_ino;     // inode
    mode_t          st_mode;    // Protection & file type
    nlink_t         st_nlink;   // Number of hard links
    uid_t           st_uid;     // User ID of owner
    gid_t           st_gid;     // Group ID of owner
    dev_t           st_rdev;    // Device type (if inode device)
    off_t           st_size;    // Total size, in bytes
    unsigned long   st_blksize; // Blocksize for filesystem I/O
    unsigned long   st_blocks;  // Number of blocks allocated
    time_t          st_atime;   // Time of last access
    time_t          st_mtime;   // Time of last modification
    time_t          st_ctime;   // Time of last change
}
int main (int argc, char **argv)
{
    struct stat stat;
    char *type, *readok;

    Stat(argv[1], &stat);
    if (S_ISREG(stat.st_mode)) // 确定文件类型,S_ISREG(m) 判断普通文件,
        type = "regular";
    else if (S_ISDIR(stat.st_mode)) // S_ISDIR(m) 判断目录文件
        type = "directory";
    else // S_ISSOCK(m) 判断网络套接字
        type = "other";

    if ((stat.st_mode & S_IRUSR)) // 检查读权限
        readok = "yes";
    else
        readok = "no";

    printf("type: %s, read: %s\n", type, readok);
    exit(0);
}
  • 读取目录内容
int main(int argc, char **argv)
{
  DIR *streamp;
  struct dirent *dep;
  streamp = Opendir(argv[0]);

  errno = 0;
  while ((dep = readdir(streamp)) != NULL) {
    printf(" 找到文件:%s\n", dep -> d_name);
  }
  if (errno != 0)
    unix_error("readdir 失败 ");
  CloseDir(streamp);
  exit(0)
}

标准输入输出

C 标准库中包含一系列高层的标准 IO 函数

  • 打开和关闭文件: fopen, fclose
  • 读取和写入字节: fread, fwrite
  • 读取和写入行: fgets, fputs
  • 格式化读取和写入: fscanf, fprintf

Standard C I/O: fopen, fdopen, fread, fwrite, fscanf, fprintf, sscanf, sprintf, fgets, fputs, fflush, fseek, fclose

Node.js 文件系统 fs 模块

Node.js 提供一组类似 UNIX(POSIX)标准的文件操作 API。 Node 导入文件系统模块 (fs) 语法如下所示

var fs = require('fs');

Node.js 文件系统(fs 模块)模块中的方法均有异步和同步版本,例如读取文件内容的函数有异步的 fs.readFile() 和同步的 fs.readFileSync()。
异步的方法函数最后一个参数为回调函数,回调函数的第一个参数包含了错误信息 (error)。
建议大家是用异步方法,比起同步,异步方法性能更高,速度更快,而且没有阻塞

var fs = require('fs');
// 异步读取
fs.readFile('./hello.txt', function(err, data) {
  if (err) {
    return console.error(err);
  }
  console.log(data.toString());
});

// 同步读取
var data = fs.readFileSync('./hello.txt');
console.log(data.toString());
  • 打开文件
fs.open(path, flags[, mode], callback(err, fd))
// fd 是返回的文件描述符
  • 获取文件信息
fs.stat(path, callback(err, stats));
// stats 是 fs.Stats 对象
  • stats 类

    查询文件信息

fs.stat(path, callback(err, stats));

一个 stats 类对象的内容

{
  dev: 16777220,   // 文件或目录所在的设备 I, 该属性值在 UNIX 系统下有效
  mode: 33188,     // 文件或目录的权限标志,采用数值形式表示
  nlink: 1,        // 文件或目录的的硬连接数量
  uid: 501,        // 文件或目录的所有者的用户 ID, 该属性值在 UNIX 系统下有效
  gid: 20,         // 文件或目录的所有者的用户组 ID, 该属性值在 UNIX 系统下有效
  rdev: 0,         // 字符设备文件或块设备文件所在设备 ID, 该属性值在 UNIX 系统下有效
  blksize: 4096,   // 块大小
  ino: 78808297,   // 文件或目录的索引编号, 该属性值仅在 UNIX 系统下有效
  size: 244,       // 文件的字节数
  blocks: 8,       // 块数
  atime: Wed May 27 2015 18:24:43 GMT+0800 (CST),  // 文件或目录的访问时间
  mtime: Wed May 27 2015 18:26:25 GMT+0800 (CST),  // 文件或目录的最后修改时间
  ctime: Wed May 27 2015 18:26:25 GMT+0800 (CST),  // 文件或目录状态的最后修改时间
  birthtime: Mon, 10 Oct 2011 23:24:11 GMT,        // 文件创建时间
  atimeMs: 1318289051000.1,      // 以单位为毫秒保存相对应时间的数字
  mtimeMs: 1318289051000.1,
  ctimeMs: 1318289051000.1,
  birthtimeMs: 1318289051000.1,
}

stats 类的方法

  1. stats.isFile() 如果是标准文件,返回 true。是目录、套接字、符号连接、或设备等返回 false
  2. stats. isDirectory() 如果是目录,返回 true
  3. stats. isBlockDevice() 如果是块设备,返回 true,大多数情况下类 UNIX 系统的块设备都位于 / dev 目录下
  4. stats. isCharacterDevice() 如果是字符设备,返回 true
  5. stats. isSymbolicLink() 如果是符号连接,返回 true。(fs.lstat() 方法返回的 stats 对象才有此方法)
  6. stats.isFIFO() 如果是 FIFO(FIFO 是 UNIX 中的一种特殊类型的命令管道),返回 true。FIFO 是 UNIX 中的一种特殊类型的命令管道
  7. stats. isSocket() 如果是 UNIX 套接字(socket),返回 true
  • 检查文件是否存在
  1. fs.stat(),如果 stats 对象存在且 stats.isFile() 为 true 才能确认要修改或删除的文件存在
var fs = require('fs');

fs.stat('/xxx', function(err, stat) {
  if (stat && stat.isFile()) {
    console.log(' 文件存在 ');
  } else {
    console.log(' 文件不存在或不是标准文件 ');
  }
});
  1. fs.access(), 检查到指定 path 路径的目录或文件的访问权限
fs.access(path[, mode], callback(err))
// 检查文件是否存在
var fs = require('fs');

fs.access('/etc/passwd', function(err) {
  console.log(err ? '文件存在' : '文件不存在 ');
});
// 检查是否对文件是否有读写权限
var fs = require('fs');

fs.access('/etc/passwd', fs.R_OK | fs.W_OK, function(err) {
  console.log(err ? '不可操作!' : '可以读 / 写 ');
});

fs.F_OK - 文件是对于进程是否可见,可以用来检查文件是否存在。也是 mode 的默认值
fs.R_OK - 文件对于进程是否可读
fs.W_OK - 文件对于进程是否可写
fs.X_OK - 文件对于进程是否可执行。(Windows 系统不可用,执行效果等同 fs.F_OK)

  • 写入文件
fs.writeFile(file, data[, options], callback(err))
  1. fd - 通过 fs.open() 方法返回的文件描述符。
  2. buffer - 数据写入的缓冲区。
  3. offset - 缓冲区写入的写入偏移量。
  4. length - 要从文件中读取的字节数。
  5. position - 文件读取的起始位置,如果 position 的值为 null,则会从当前文件指针的位置读取。
  6. callback - 回调函数,有三个参数 err, bytesRead, buffer,err 为错误信息, bytesRead 表示读取的字节数,buffer 为缓冲区对象
var fs = require('fs');
var buf = new Buffer(1024);

console.log(' 准备打开已存在的文件!');
fs.open('input.txt', 'r+', function(err, fd) {
  if (err) {
    return console.error(err);
  }
  console.log(' 文件打开成功!');
  console.log(' 准备读取文件:');
  fs.read(fd, buf, 0, buf.length, 0, function(err, bytes) {
    if (err) {
      console.log(err);
    }
    console.log(bytes + ' 字节被读取 ');

    // 仅输出读取的字节
    if (bytes > 0) {
      console.log(buf.slice(0, bytes).toString());
    }
  });
});

// fs.write(fd, buffer, offset, length[, position], callback)
  • 关闭文件
fs.close(fd, callback());
  • 截取文件
fs.ftruncate(fd, len, callback());
  • 删除文件
fs.unlink(path, callback());
  • 创建目录
fs.mkdir(path[, mode], callback)
  • 读取目录
fs.readdir(path, callback(err, files));
// files 为目录下的文件数组列表
  • 删除目录
fs.rmdir(path, callback());
  • 检测给定的路径是否存在
fs.existsSync(path); // 异步版本已无效
  • 追加文件内容
fs.appendFile(filename, data[, options], callback(err))

几条查询目录信息终端命令

$ find 路径 -type f|wc -l // 统计文件数
$ find 路径 -type d|wc -l // 统计目录数

$ du -sh 路径 // 统计大小
$ df -h /    // 查询磁盘使用量

$ ls -lR 路径 | grep "^d"|wc -l // 统计目录数
$ ls -lR 路径 | grep "^-"|wc -l // 统计文件数
文章目录
  1. Unix I/O 是系统底层数据操作
  2. 标准输入输出
  3. Node.js 文件系统 fs 模块
  4. 几条查询目录信息终端命令