MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

JavaScript在Node中批量操作文件的方法

2021-03-055.2k 阅读

使用 fs 模块进行基础文件操作

在 Node.js 中,fs 模块是操作文件的核心。它提供了一系列同步和异步的方法来处理文件系统相关的任务。在批量操作文件之前,我们先来了解一下如何使用 fs 模块进行单个文件的读取、写入和创建等基本操作。

读取文件

异步读取文件

fs.readFile 方法用于异步读取文件。它接受三个参数:文件名、编码格式(可选)和回调函数。当读取操作完成时,回调函数会被调用,其第一个参数是可能发生的错误,第二个参数是读取到的数据。

const fs = require('fs');

fs.readFile('example.txt', 'utf8', (err, data) => {
    if (err) {
        console.error(err);
        return;
    }
    console.log(data);
});

在上述代码中,我们尝试读取名为 example.txt 的文件,并指定编码格式为 utf8。如果读取过程中出现错误,错误信息会被打印到控制台。如果成功读取,文件内容会被打印出来。

同步读取文件

fs.readFileSync 方法用于同步读取文件。它接受两个参数:文件名和编码格式(可选)。该方法会阻塞当前线程,直到文件读取完成,然后返回读取到的数据。如果发生错误,会抛出异常。

const fs = require('fs');

try {
    const data = fs.readFileSync('example.txt', 'utf8');
    console.log(data);
} catch (err) {
    console.error(err);
}

这里使用 try - catch 块来捕获可能发生的错误。同步读取适用于需要立即获取文件内容且不希望阻塞其他操作的场景,但在处理大文件或 I/O 密集型任务时可能会影响性能。

写入文件

异步写入文件

fs.writeFile 方法用于异步写入文件。它接受三个参数:文件名、要写入的数据和回调函数。当写入操作完成时,回调函数会被调用,其第一个参数是可能发生的错误。

const fs = require('fs');

const content = 'This is some new content.';
fs.writeFile('newFile.txt', content, err => {
    if (err) {
        console.error(err);
        return;
    }
    console.log('File written successfully');
});

在这段代码中,我们将字符串 This is some new content. 写入名为 newFile.txt 的文件。如果写入成功,会在控制台打印提示信息。

同步写入文件

fs.writeFileSync 方法用于同步写入文件。它接受两个参数:文件名和要写入的数据。该方法会阻塞当前线程,直到文件写入完成。如果发生错误,会抛出异常。

const fs = require('fs');

const content = 'This is some new content (sync).';
try {
    fs.writeFileSync('newFileSync.txt', content);
    console.log('File written synchronously successfully');
} catch (err) {
    console.error(err);
}

同样,这里使用 try - catch 块来处理可能的错误。同步写入在一些对写入操作顺序要求严格的场景中比较有用。

创建目录

异步创建目录

fs.mkdir 方法用于异步创建目录。它接受两个参数:目录路径和回调函数。当目录创建完成时,回调函数会被调用,其第一个参数是可能发生的错误。

const fs = require('fs');

fs.mkdir('newDirectory', err => {
    if (err) {
        console.error(err);
        return;
    }
    console.log('Directory created successfully');
});

此代码尝试创建名为 newDirectory 的目录,如果创建成功会打印相应提示。

同步创建目录

fs.mkdirSync 方法用于同步创建目录。它接受一个参数:目录路径。该方法会阻塞当前线程,直到目录创建完成。如果发生错误,会抛出异常。

const fs = require('fs');

try {
    fs.mkdirSync('newDirectorySync');
    console.log('Directory created synchronously successfully');
} catch (err) {
    console.error(err);
}

这里使用 try - catch 块来捕获可能的错误。同步创建目录适用于需要确保目录立即创建完成的场景。

批量读取文件

在实际应用中,我们经常需要批量读取一组文件。例如,在一个项目中,可能有多个配置文件需要读取并合并,或者有一组日志文件需要分析。

使用 fs.readdir 异步批量读取文件

fs.readdir 方法用于异步读取指定目录下的所有文件和目录的名称。它接受两个参数:目录路径和回调函数。回调函数的第一个参数是可能发生的错误,第二个参数是文件名数组。

const fs = require('fs');
const path = require('path');

const directoryPath = './exampleDir';

fs.readdir(directoryPath, (err, files) => {
    if (err) {
        console.error(err);
        return;
    }

    files.forEach(file => {
        const filePath = path.join(directoryPath, file);
        fs.readFile(filePath, 'utf8', (readErr, data) => {
            if (readErr) {
                console.error(readErr);
                return;
            }
            console.log(`File: ${file}, Content: ${data}`);
        });
    });
});

在上述代码中,我们首先使用 fs.readdir 读取 exampleDir 目录下的所有文件和目录名称。然后,对于每个文件,我们使用 path.join 方法构建完整的文件路径,再使用 fs.readFile 读取文件内容并打印。

使用 fs.readdirSync 同步批量读取文件

fs.readdirSync 方法用于同步读取指定目录下的所有文件和目录的名称。它接受一个参数:目录路径,并返回文件名数组。

const fs = require('fs');
const path = require('path');

const directoryPath = './exampleDir';

try {
    const files = fs.readdirSync(directoryPath);
    files.forEach(file => {
        const filePath = path.join(directoryPath, file);
        const data = fs.readFileSync(filePath, 'utf8');
        console.log(`File: ${file}, Content: ${data}`);
    });
} catch (err) {
    console.error(err);
}

这里我们使用 fs.readdirSync 同步读取目录下的文件列表,然后同步读取每个文件的内容并打印。同步方式在文件数量较少且希望快速获取所有文件内容时比较方便,但如果文件数量较多或文件较大,可能会导致主线程阻塞。

递归读取目录下所有文件

有时候,我们需要读取一个目录及其子目录下的所有文件。这就需要使用递归的方式来处理。

const fs = require('fs');
const path = require('path');

function readAllFiles(directory) {
    const files = [];

    function readDirectory(dir) {
        const items = fs.readdirSync(dir);
        items.forEach(item => {
            const itemPath = path.join(dir, item);
            const stats = fs.statSync(itemPath);
            if (stats.isDirectory()) {
                readDirectory(itemPath);
            } else {
                files.push(itemPath);
            }
        });
    }

    readDirectory(directory);
    return files;
}

const allFiles = readAllFiles('./exampleDir');
console.log(allFiles);

在上述代码中,我们定义了 readAllFiles 函数,它接受一个目录路径作为参数。在函数内部,我们又定义了 readDirectory 函数用于递归读取目录。fs.statSync 方法用于获取文件或目录的状态信息,通过判断 stats.isDirectory() 来决定是继续递归读取子目录还是将文件路径添加到 files 数组中。最后返回包含所有文件路径的数组。

批量写入文件

批量写入文件在很多场景下也非常有用,比如生成一组配置文件或者将数据分别写入多个日志文件。

异步批量写入文件

我们可以结合 fs.writeFile 和循环来实现异步批量写入文件。

const fs = require('fs');

const dataArray = ['data1', 'data2', 'data3'];
const fileNames = ['file1.txt', 'file2.txt', 'file3.txt'];

dataArray.forEach((data, index) => {
    const fileName = fileNames[index];
    fs.writeFile(fileName, data, err => {
        if (err) {
            console.error(err);
            return;
        }
        console.log(`${fileName} written successfully`);
    });
});

在这段代码中,我们有一个数据数组 dataArray 和对应的文件名数组 fileNames。通过 forEach 循环,我们将每个数据写入对应的文件中。由于 fs.writeFile 是异步的,多个写入操作会并发执行,这样可以提高写入效率,但也需要注意可能出现的竞争条件。

同步批量写入文件

同步批量写入文件可以通过 fs.writeFileSync 和循环来实现。

const fs = require('fs');

const dataArray = ['data1 (sync)', 'data2 (sync)', 'data3 (sync)'];
const fileNames = ['file1Sync.txt', 'file2Sync.txt', 'file3Sync.txt'];

try {
    dataArray.forEach((data, index) => {
        const fileName = fileNames[index];
        fs.writeFileSync(fileName, data);
        console.log(`${fileName} written synchronously successfully`);
    });
} catch (err) {
    console.error(err);
}

这里使用 fs.writeFileSync 同步写入文件。同步方式会依次执行每个写入操作,虽然代码逻辑相对简单,但如果写入操作较多,会阻塞主线程,影响程序的响应性。

处理写入错误和并发控制

在异步批量写入文件时,由于多个写入操作可能并发执行,处理错误和控制并发数量是很重要的。我们可以使用 Promiseasync/await 来更好地管理这些情况。

const fs = require('fs');
const path = require('path');
const { promisify } = require('util');

const writeFileAsync = promisify(fs.writeFile);

async function writeFilesInBatch(dataArray, fileNames, maxConcurrent = 3) {
    const tasks = [];
    const semaphore = Array.from({ length: maxConcurrent }, () => true);

    for (let i = 0; i < dataArray.length; i++) {
        const data = dataArray[i];
        const fileName = fileNames[i];
        const task = (async () => {
            let availableIndex;
            for (let j = 0; j < semaphore.length; j++) {
                if (semaphore[j]) {
                    availableIndex = j;
                    semaphore[j] = false;
                    break;
                }
            }

            try {
                await writeFileAsync(path.join(__dirname, fileName), data);
                console.log(`${fileName} written successfully`);
            } catch (err) {
                console.error(err);
            } finally {
                semaphore[availableIndex] = true;
            }
        })();
        tasks.push(task);
    }

    await Promise.all(tasks);
}

const dataArray = ['data1', 'data2', 'data3', 'data4', 'data5'];
const fileNames = ['file1.txt', 'file2.txt', 'file3.txt', 'file4.txt', 'file5.txt'];

writeFilesInBatch(dataArray, fileNames);

在上述代码中,我们使用 promisifyfs.writeFile 转换为返回 Promise 的函数 writeFileAsyncwriteFilesInBatch 函数接受数据数组、文件名数组和最大并发数作为参数。我们使用一个信号量数组 semaphore 来控制并发数量,每次有任务执行时,获取一个可用的信号量,任务完成后释放。通过 Promise.all 等待所有任务完成。这样既可以提高写入效率,又能有效处理错误和控制并发。

批量删除文件

在某些情况下,我们可能需要批量删除一组文件,比如清理临时文件或者删除不再需要的日志文件。

异步批量删除文件

fs.unlink 方法用于异步删除文件。我们可以结合 fs.readdirfs.unlink 来实现异步批量删除文件。

const fs = require('fs');
const path = require('path');

const directoryPath = './exampleDirToDelete';

fs.readdir(directoryPath, (err, files) => {
    if (err) {
        console.error(err);
        return;
    }

    files.forEach(file => {
        const filePath = path.join(directoryPath, file);
        fs.unlink(filePath, err => {
            if (err) {
                console.error(err);
                return;
            }
            console.log(`${file} deleted successfully`);
        });
    });
});

在这段代码中,我们首先读取指定目录下的所有文件,然后对每个文件使用 fs.unlink 进行异步删除。如果删除成功,会在控制台打印提示信息。

同步批量删除文件

fs.unlinkSync 方法用于同步删除文件。我们可以使用 fs.readdirSyncfs.unlinkSync 来实现同步批量删除文件。

const fs = require('fs');
const path = require('path');

const directoryPath = './exampleDirToDeleteSync';

try {
    const files = fs.readdirSync(directoryPath);
    files.forEach(file => {
        const filePath = path.join(directoryPath, file);
        fs.unlinkSync(filePath);
        console.log(`${file} deleted synchronously successfully`);
    });
} catch (err) {
    console.error(err);
}

这里我们同步读取目录下的文件列表,然后同步删除每个文件。同步删除方式简单直接,但如果文件数量较多,会阻塞主线程。

递归删除目录及其所有文件

要删除一个目录及其所有子目录和文件,我们需要使用递归的方式。

const fs = require('fs');
const path = require('path');

function deleteDirectory(directory) {
    if (!fs.existsSync(directory)) return;

    const items = fs.readdirSync(directory);
    items.forEach(item => {
        const itemPath = path.join(directory, item);
        const stats = fs.statSync(itemPath);
        if (stats.isDirectory()) {
            deleteDirectory(itemPath);
        } else {
            fs.unlinkSync(itemPath);
        }
    });
    fs.rmdirSync(directory);
}

const directoryToDelete = './exampleDirToDeleteRecursive';
deleteDirectory(directoryToDelete);

在上述代码中,deleteDirectory 函数首先检查目录是否存在。然后读取目录下的所有项目,对于子目录,递归调用 deleteDirectory 进行删除,对于文件,使用 fs.unlinkSync 进行删除。最后,使用 fs.rmdirSync 删除空目录。

批量重命名文件

批量重命名文件在整理文件或者对项目文件进行重构时经常用到。

异步批量重命名文件

fs.rename 方法用于异步重命名文件或目录。我们可以结合 fs.readdirfs.rename 来实现异步批量重命名文件。

const fs = require('fs');
const path = require('path');

const directoryPath = './exampleDirToRename';

fs.readdir(directoryPath, (err, files) => {
    if (err) {
        console.error(err);
        return;
    }

    files.forEach(file => {
        const oldFilePath = path.join(directoryPath, file);
        const newFileName = `new_${file}`;
        const newFilePath = path.join(directoryPath, newFileName);
        fs.rename(oldFilePath, newFilePath, err => {
            if (err) {
                console.error(err);
                return;
            }
            console.log(`${file} renamed to ${newFileName} successfully`);
        });
    });
});

在这段代码中,我们读取指定目录下的所有文件,然后为每个文件生成一个新的文件名(这里简单地在原文件名前加上 new_),使用 fs.rename 进行异步重命名。

同步批量重命名文件

fs.renameSync 方法用于同步重命名文件或目录。我们可以使用 fs.readdirSyncfs.renameSync 来实现同步批量重命名文件。

const fs = require('fs');
const path = require('path');

const directoryPath = './exampleDirToRenameSync';

try {
    const files = fs.readdirSync(directoryPath);
    files.forEach(file => {
        const oldFilePath = path.join(directoryPath, file);
        const newFileName = `sync_new_${file}`;
        const newFilePath = path.join(directoryPath, newFileName);
        fs.renameSync(oldFilePath, newFilePath);
        console.log(`${file} renamed to ${newFileName} synchronously successfully`);
    });
} catch (err) {
    console.error(err);
}

这里我们同步读取目录下的文件列表,然后同步重命名每个文件。同步重命名方式虽然简单,但如果文件数量较多,会阻塞主线程。

按规则批量重命名文件

有时候,我们需要根据特定的规则来批量重命名文件。例如,将所有以 old_ 开头的文件重命名为去掉 old_ 前缀的名称。

const fs = require('fs');
const path = require('path');

const directoryPath = './exampleDirWithRules';

fs.readdir(directoryPath, (err, files) => {
    if (err) {
        console.error(err);
        return;
    }

    files.forEach(file => {
        if (file.startsWith('old_')) {
            const oldFilePath = path.join(directoryPath, file);
            const newFileName = file.slice(4);
            const newFilePath = path.join(directoryPath, newFileName);
            fs.rename(oldFilePath, newFilePath, err => {
                if (err) {
                    console.error(err);
                    return;
                }
                console.log(`${file} renamed to ${newFileName} successfully according to rule`);
            });
        }
    });
});

在上述代码中,我们首先检查文件名是否以 old_ 开头,如果是,则去掉前缀生成新的文件名并进行重命名。这样就实现了按特定规则批量重命名文件。

通过以上对 Node.js 中 JavaScript 批量操作文件的方法介绍,我们可以看到,无论是读取、写入、删除还是重命名文件,都可以通过 fs 模块结合适当的逻辑来实现。在实际应用中,我们需要根据具体的需求和场景选择合适的同步或异步方式,并注意处理可能出现的错误和并发问题。