edo1z blog

プログラミングなどに関するブログです

Node.jsでファイルの読み書き

Node.jsでファイルの読み書きします。

File System | Node.js v10.11.0 Documentation

fsモジュール

fsモジュールを使って読み書きします。非同期コールバック、同期、非同期プロミスバージョンという感じで、同じ処理に3つの関数がある場合が多いっぽい。基本非同期を使うことが推奨されている。

非同期関数たち

readもwirteも、同じような種類の関数がある。readはファイルの一部を任意の長さで取得するようなことができる。readFileはファイルを全部取得する。readStreamは、つなぎっぱにして頭から順番に読み込める。何かの処理を連続して行ったり、ファイルサイズが大きい場合などは、readStreamがいい。下記は非同期コールバック関数たちですが、下記に対応した、Promiseバージョンもある。

読み込み関数

書き込み関数

使ってみる

fs.readFile

ファイルをまるっと読み込むものなので、使い方は単純です。ファイル名の次の引数に文字エンコードをつけると、文字列が取得できます。

fs.readFile('message.txt', (err, data) => {
  if (err) throw err
  console.log(data)
})
fs.readFile('message.txt', 'utf8', (err, data) => {
  if (err) throw err
  console.log(data)
})

結果

<Buffer 61 62 63 64 0a 31 32 33 34 0a e3 81 82 e3 81 84 e3 81 86 e3 81 88 0a>
abcd
1234
あいうえ

fs.read

特定の位置だけ取得したいといったときに使うのかな?最初の500バイトだけ取得したいとか。

const hoge = Buffer.alloc(100)
fs.open('wp.xml', 'r', (err, fd) => {
  if (err) throw err
  fs.read(fd, hoge, 0, 50, 0, (err, bytesRead, buffer) => {
    if (err) throw err
    console.log(buffer.toString('utf8', 0, 100))
  })
  fs.close(fd, err => {
    if (err) throw err
  })
})

結果

<?xml version="1.0" encoding="UTF-8" ?>
<!-- This 

fs.ReadStream

順番にちょっとずつ取得するやつ。メモリに優しい。

const stream = fs.createReadStream('wp.xml')
let count = 0
let totalSize = 0
stream.on('data', chunk => {
  console.log(chunk.toString('utf8', 0, 100))
  count++
  totalSize += chunk.length
})
stream.on('end', () => {
  console.log(count)
  console.log(totalSize)
})

結果

(.... 沢山の文字列...)
123
8004493

あとは、pipeというのも使える。

const r = fs.createReadStream('wp.xml')
const z = zlib.createGzip()
const w = fs.createWriteStream('wp.gz')
r.pipe(z).pipe(w)

結果は、wp.gzが作成されます。

fs.writeFile

ファイルに書きます。まるっと書きます。

const data = 'こんにちは'
fs.writeFile('hello.txt', data, err => {
  if (err) throw err
})
fs.readFile('hello.txt', 'utf8', (err, data) => {
  if (err) throw err
  console.log(data)
})

結果

こんにちは

fs.wirte

一部だけ書き換えるみたいなときに使うのかな?効率よく、「こんにチは」に変えるみたいな。

fs.open('hello.txt', 'r+', (err, fd) => {
  if (err) throw err
  fs.write(fd, 'チ', 9, 'utf8', (err, written, str) => {
    if (err) throw err
  })
})

おーできた。こんにチはになった。でも、r+とかのモードによって、全然挙動が違うし、OSによっても全然違うらしいから、注意が必要そう。。しかも、これ上書きされるけど、純粋に1文字追加したい場合はどうしたらいいんだろう?

fs.open('hello.txt', 'r+', (err, fd) => {
  if (err) throw err
  const chi = Buffer.from('あいチえお')
  console.log(chi)
  fs.write(fd, chi, 6, 3, 9, (err, bytesWritten, buffer) => {
    if (err) throw err
  })
})

ちなみに、これでもできた。ちょっとだけfs.writeの引数が違う。でも、やっぱり純粋1文字挿入はできない。挿入というのは、挿入する箇所以降の文字列をコピーして、場所を移動させた上で、上記を実行するみたいなことが必要なのかもしれん。

fs.WriteStream

ファイルを完全上書きでよければ、こんな感じで使える。

const stream = fs.createWriteStream('hello.txt')
const arr = [1, 2, 3, 4, 5]
arr.forEach(num => stream.write(num + "\n"))
stream.end()

下記のようにモードの設定ができる

const arr = [6, 7]
const options = {
  'flags': 'r+',
  'encoding': 'utf8'
}
const stream = fs.createWriteStream('hello.txt', options)
arr.forEach(num => stream.write(num + "\n"))
stream.end()

pipe

pipeは便利そうなので、もうちょい試してみる。

const read = fs.createReadStream('wp.xml')
const write = fs.createWriteStream('new.xml')
read.pipe(write)

これだと、単純にwp.xmlの内容が、new.xmlに書き込まれる。readとwriteの間に何かを挟むことで便利になります。例えば「全部大文字に変換してから書き込む」というのをやってみたいと思います。

const reader = fs.createReadStream('hello.txt')
const writer = fs.createWriteStream('new.txt')
const { Transform } = require('stream')
class UpperCaseStream extends Transform {
  write (data) {
    data = data.toString().toUpperCase()
    this.emit('data', data)
  }
}
const upper = new UpperCaseStream()
reader.pipe(upper).pipe(writer)

これでできた。