この記事は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_)までお願いいたします。