Node.js 文件操作

504 阅读5分钟

Node.js 中获取文件描述符

文件描述符概念

Linux 系统中,把一切都看做是文件,当进程打开现有文件或创建新文件时,内核向进程返回一个文件描述符,文件描述符就是内核为了高效管理已被打开的文件所创建的索引,用来指向被打开的文件,所有执行 I/O 操作的系统调用都会通过文件描述符。

通俗的讲,形式上就是一个 index,指向被打开的文件,便于执行文件 I/O 操作。

获取文件描述符

const fs = require('fs')


fs.open('/Users/joe/test.txt', 'r', (err, fd) => {
  //fd 是文件描述符。
})

// 同步方式
try {
  const fd = fs.openSync('/Users/joe/test.txt', 'r')
} catch (err) {
  console.error(err)
}

文件属性

const fs = require('fs')
fs.stat('/Users/joe/test.txt', (err, stats) => {
  if (err) {
    console.error(err)
    return
  }


  stats.isFile() //true
  stats.isDirectory() //false
  stats.isSymbolicLink() //false
  stats.size //1024000 //= 1MB
})

文件读取

const fs = require('fs')


fs.readFile('/Users/joe/test.txt', 'utf8' , (err, data) => {
  if (err) {
    console.error(err)
    return
  }
  console.log(data)
})

文件写入

const fs = require('fs')


const content = '一些内容'


fs.writeFile('/Users/joe/test.txt', content, err => {
  if (err) {
    console.error(err)
    return
  }
  //文件写入成功。
})

可以通过指定标志来修改默认的行为:

fs.writeFile('/Users/joe/test.txt', content, { flag: 'a+' }, err => {})

可使用的标志有:

  • r+ 打开文件用于读写。
  • w+ 打开文件用于读写,将流定位到文件的开头。如果文件不存在则创建文件。
  • a 打开文件用于写入,将流定位到文件的末尾。如果文件不存在则创建文件。
  • a+ 打开文件用于读写,将流定位到文件的末尾。如果文件不存在则创建文件。

(可以在 nodejs.cn/api/fs.html… 中查看更多标志)

追加到文件

const content = '一些内容'


fs.appendFile('file.log', content, err => {
  if (err) {
    console.error(err)
    return
  }
  //完成!
})

Nodejs 流

所有这些方法都是在将全部内容写入文件之后才会将控制权返回给程序(在异步的版本中,这意味着执行回调)。

在这种情况下,更好的选择是使用流写入文件的内容。

什么是流

流是为 Node.js 应用程序提供动力的基本概念之一。

它们是一种以高效的方式处理读/写文件、网络通信、或任何类型的端到端的信息交换。

流不是 Node.js 特有的概念。 它们是几十年前在 Unix 操作系统中引入的,程序可以通过管道运算符(|)对流进行相互交互。

例如,在传统的方式中,当告诉程序读取文件时,这会将文件从头到尾读入内存,然后进行处理。

使用流,则可以逐个片段地读取并处理(而无需全部保存在内存中)。

Node.js 的 stream 模块 提供了构建所有流 API 的基础。 所有的流都是 EventEmitter 的实例。

使用 Node.js 的 fs 模块,可以读取文件,并在与 HTTP 服务器建立新连接时通过 HTTP 提供文件,如果文件很大,则该操作会花费较多的时间。

以下是使用流编写:

const http = require('http')
const fs = require('fs')


const server = http.createServer((req, res) => {
  const stream = fs.createReadStream(__dirname + '/data.txt')
  stream.pipe(res)
})
server.listen(3000)

pipe()

上面的示例使用了 stream.pipe(res) 这行代码:在文件流上调用 pipe() 方法。

该代码的作用是什么? 它获取来源流,并将其通过管道传输到目标流。

在来源流上调用它,在该示例中,文件流通过管道传输到 HTTP 响应。

流驱动的 Node.js API

由于它们的优点,许多 Node.js 核心模块提供了原生的流处理功能,最值得注意的有:

  • process.stdin 返回连接到 stdin 的流。
  • process.stdout 返回连接到 stdout 的流。
  • process.stderr 返回连接到 stderr 的流。
  • fs.createReadStream() 创建文件的可读流。
  • fs.createWriteStream() 创建到文件的可写流。
  • net.connect() 启动基于流的连接。
  • http.request() 返回 http.ClientRequest 类的实例,该实例是可写流。
  • zlib.createGzip() 使用 gzip(压缩算法)将数据压缩到流中。
  • zlib.createGunzip() 解压缩 gzip 流。
  • zlib.createDeflate() 使用 deflate(压缩算法)将数据压缩到流中。
  • zlib.createInflate() 解压缩 deflate 流。

流分为四类:

  • Readable: 可以通过管道读取、但不能通过管道写入的流(可以接收数据,但不能向其发送数据)。 当推送数据到可读流中时,会对其进行缓冲,直到使用者开始读取数据为止。
  • Writable: 可以通过管道写入、但不能通过管道读取的流(可以发送数据,但不能从中接收数据)。
  • Duplex: 可以通过管道写入和读取的流,基本上相对于是可读流和可写流的组合。
  • Transform: 类似于双工流、但其输出是其输入的转换的转换流。

如何创建可读流

从 stream 模块获取可读流,对其进行初始化并实现 readable._read() 方法。

首先创建流对象:

const Stream = require('stream')
const readableStream = new Stream.Readable()

然后实现 _read

readableStream._read = () => {}

也可以使用 read 选项实现 _read

const readableStream = new Stream.Readable({
  read() {}
})

现在,流已初始化,可以向其发送数据了:

readableStream.push('hi!')
readableStream.push('ho!')

如何创建可写流

若要创建可写流,需要继承基本的 Writable 对象,并实现其 _write() 方法。

首先创建流对象:

const Stream = require('stream')
const writableStream = new Stream.Writable()

然后实现 _write

writableStream._write = (chunk, encoding, next) => {
  console.log(chunk.toString())
  next()
}

现在,可以通过以下方式传输可读流:

process.stdin.pipe(writableStream)

如何从可读流中获取数据

如何从可读流中读取数据? 使用可写流:

const Stream = require('stream')


const readableStream = new Stream.Readable({
  read() {}
})
const writableStream = new Stream.Writable()


writableStream._write = (chunk, encoding, next) => {
  console.log(chunk.toString())
  next()
}


readableStream.pipe(writableStream)


readableStream.push('hi!')
readableStream.push('ho!')

也可以使用 readable 事件直接地消费可读流:

JS
readableStream.on('readable', () => {
  console.log(readableStream.read())
})

如何发送数据到可写流

使用流的 write() 方法:

writableStream.write('hey!\n')

使用信号通知已结束写入的可写流

使用 end() 方法:

const Stream = require('stream')


const readableStream = new Stream.Readable({
  read() {}
})
const writableStream = new Stream.Writable()


writableStream._write = (chunk, encoding, next) => {
  console.log(chunk.toString())
  next()
}


readableStream.pipe(writableStream)


readableStream.push('hi!')
readableStream.push('ho!')


writableStream.end()

参考

文件描述符(File Descriptor)简介

使用 Node.js 读取文件

使用 Node.js 写入文件

Node.js 流