この記事はNode.js Advent Calendar 2018の2日目の記事です。
ちょっとしたネタですが、Node.js v10.10から入ったfs.readdirのwithFileTypesオプションとfs.Direntについて紹介したいと思います。
TL;DR
- 特定のディレクトリ配下のファイル一覧を表示する方法を紹介
- 読み込んだディレクトリのファイル種別がファイルかディレクトリか判定する方法を紹介
-
fs.statを使った従来の方法 - Node.js v10.10.0から使える
fs.readdir(またはfs.readdirSync)のwithFileTypesとfs.Direntを使った方法
-
fs.statを使った判定方法
例えばNode.jsを使ってディレクトリ配下のファイル一覧を表示したいとき以下のようにfs.readdirとfs.statを使って書くことができます。
const fs = require('fs');
const path = require('path');
const showFiles = (dirpath, callback) => {
fs.readdir(dirpath, (err, files) => {
if (err) {
console.error(err);
return;
}
for (const file of files) {
const fp = path.join(dirpath, file);
fs.stat(fp, (err, stats) => {
if (err) {
console.error(err);
return;
}
if (stats.isDirectory()) {
showFiles(fp, callback);
} else {
callback(fp);
}
});
}
});
}
showFiles(process.argv[2], console.log);
fs.readdirで特定のディレクトリ配下のファイルとディレクトリの一覧を取得し、それをfs.statを使ってディレクトリかどうか判定しています。
もちろん上記のコードで問題ありません。
しかし、このコードはfs.readdirとfs.statを使うことでFile Systemを二回コールしています。
パフォーマンス的には改善の余地がありそうです。
そこでNode.js v10.10.0から新しくfs.readdirにwithFileTypesというオプション引数が追加されました。
fs.readdirのwithFileTypesオプションを使った判定方法
追加されたオプションwithFileTypesをtrueにするとこれまでのようにファイル名やディレクトリ名の文字列の配列を取得するのではなく、fs.Direntオブジェクトの配列を取得することができます。
実装されたPRは→https://github.com/nodejs/node/pull/22020
で、結局何ができるのかというと上記のようにfs.readdirとfs.statより簡潔なコードになり、実行速度も高速なコードを書くことが可能になります。
const fs = require('fs');
const path = require('path');
const showFiles = (dirpath, callback) => {
fs.readdir(dirpath, {withFileTypes: true}, (err, dirents) => {
if (err) {
console.error(err);
return;
}
for (const dirent of dirents) {
const fp = path.join(dirpath, dirent.name);
if (dirent.isDirectory()) {
showFiles(fp, callback);
} else {
callback(fp);
}
}
});
}
showFiles(process.argv[2], console.log);
direntとは
これまでに出てきたfs.Direntのdirentですが、ディレクトリエントリと呼ばれるファイルやディレクトリの情報を持った構造体です。
UNIX系のOSを使っている方であればmanで説明を読むことができます。
$ man dirent
日本語の説明は https://nxmnpg.lemoda.net/ja/5/dirent を参考にしてみてください。
Node.jsの非同期I/Oやイベントループに使われているlibuvにもuv_dirent_tとうenumが存在していてます。
http://docs.libuv.org/en/v1.x/fs.html
fs.readdirで取得できるfs.DirentクラスのisDirectory関数とかはこのuv_dirent_tが持つ値を使って判定しています。
パフォーマンス
withFileTypesオプションを使うことでパフォーマンスはどれくらい良くなったのか計測してみました。
計測対象は筆者のローカルにcloneしているnodejs/nodeです。
あと、標準出力すると時間がかかるので表示結果は/dev/nullに捨てています。
5回計測した結果を載せます。
fs.stat
まずは上記のfs.statを使ったコードの計測結果です。
$ time node stat-test.js /path/to/node > /dev/null
real 0m1.425s
user 0m1.394s
sys 0m1.109s
real 0m1.188s
user 0m1.205s
sys 0m0.816s
real 0m1.302s
user 0m1.383s
sys 0m1.059s
real 0m1.242s
user 0m1.252s
sys 0m0.929s
real 0m1.420s
user 0m1.373s
sys 0m0.931s
fs.readdir withFileTypes
次に上記のfs.readdirのwithFileTypesオプションを使ったコードの計測結果です。
$ time node withFileTypes-test.js /path/to/node > /dev/null
real 0m0.666s
user 0m0.668s
sys 0m0.385s
real 0m0.628s
user 0m0.681s
sys 0m0.362s
real 0m0.694s
user 0m0.697s
sys 0m0.372s
real 0m0.657s
user 0m0.697s
sys 0m0.369s
real 0m0.732s
user 0m0.742s
sys 0m0.392s
手元で試した限りでは結構差がある結果になりました。
v10.10.0以降でファイルかディレクトリか判定するときはfs.statではなくfs.readdirのwithFileTypesオプションを使っていくのが良いかと思います。
以上になります。最後までお読みいただきありがとうございました。
この記事の不備や質問についてはコメント欄またはTwitter(@shisama_)までお願いいたします。