Summary of the fs

In NodeJS, all file operations are implemented through the FS core module, including file directory creation, deletion, query, and file reading and writing. In FS module, all methods are divided into synchronous and asynchronous implementations, and the method with sync suffix is synchronous method. Methods that do not have the sync suffix are asynchronous methods. Before understanding the method of file operation, there are some pre-knowledge about the system and the file, such as the file permission bit mode, the identification bit flag, the file descriptor fd, etc., so these concepts will be clarified before understanding the FS method.

Permission bit mode

Since fs module needs to operate files, operation permissions will be involved, so it is necessary to first know what file permissions are and what permissions are available.

File permission table:

To allocate File owner File Group Other users
Permission to a read write perform read write perform read write perform
Character representation

r w x r w x r w x
The figures show that the 4 2 1 4 2 1 4 2 1

In the above table, we can see in the system for the distribution of three types of rights, namely the file owner (himself), documents belong to group (family), and other users (strangers), file permissions operation is divided into three kinds, read, write, and execute, digital representation as an octal number, has the permissions octal number 4, 2, 1 respectively, do not have permissions to 0.

If you are not familiar with Git and Linux commands, you can see the Git command summary (from zero to all).

Drwxr-xr-x 1 PandaShen 197121 0 Jun 28 14:41 Core

-rw-r–r– 1 PandaShen 197121 293 Jun 23 17:44 index.md

In the directory information above, it is easy to see the username, creation time, and filename, but the most important item is the first (ten-digit character).

First on behalf of the file or folder, folder, the d beginning – the beginning of the file, and the back nine on behalf of the current user, the user belongs to group and the other user permissions, per three division, representing read (r), write (w) and execute (x), – on behalf of no current corresponding permissions.

The permission parameter mode applies to Linux and Unix operating systems. The default permission of Windows is readable, writable, and unexecutable. Therefore, the permission bit is expressed in 0o666 and 438 in decimal notation.

r w r r
4 2 0 4 0 0 4 0 0
6 4 4

Identify a flag

In NodeJS, the identifier bits represent operations on files, such as readable, writable, readable and writable, etc. In the following table, the identifier bits of file operations and their corresponding meanings are represented.

symbol meaning
r Reads the file and throws an exception if the file does not exist.
r+ Reads and writes to a file, throwing an exception if the file does not exist.
rs Reading and writing files instructs the operating system to bypass the local file system cache.
w If the file does not exist, it will be created. If the file exists, it will be cleared and written.
wx Write file, open it in exclusive mode.
w+ The file is read and written to. If the file does not exist, the file is created. If the file exists, the file is written to.
wx+ andw+Similarly, exclusive way to open.
a Append write, create file if file does not exist.
ax withaSimilarly, exclusive way to open.
a+ Read and append to, create if not present.
ax+ witha+Similarly, exclusive way to open.

The table above shows the specific characters and meanings of these flag bits. However, flag is not often used and is not easy to remember, so a method to speed up memory is summarized below.

  • R: read
  • W: write
  • S: synchronous
  • + : Adds the reverse operation
  • X: Exclusivity

The difference between r+ and w+ is that r+ will not create a file when the file does not exist, but will throw an exception, but w+ will create the file; If the file exists, r+ will not automatically empty the file, but W + will automatically empty the contents of the existing file.

File descriptor fd

Operating system will be open for each file allocation a numerical identifier, called the file descriptor file operations use the file descriptor to identify and track each specific files, Window system USES a different concept but a similar mechanism to track resources, for the convenience of users, NodeJS abstract the differences between the different operating systems, A file descriptor that assigns a numeric value to all open files.

In NodeJS, file descriptors are incremented each time a file is operated on. File descriptors usually start at 3, because there are three special descriptors: 0, 1, and 2. They represent process.stdin (standard input), process.stdout (standard output), and process.stderr (error output).

The basic method of file manipulation

The basic method of file operation is to operate the file as a whole, that is, to operate the entire file data directly in memory, such as reading, writing, copying and adding. Due to the limited memory capacity of the computer, file operation needs to consider performance, so these methods only for the operation of the small memory of the file.

1. File reading

(1) Synchronous reading method readFileSync

ReadFileSync takes two arguments:

  • The first parameter is the path to read the file or the file descriptor;
  • The second parameter isoptions, the default value isnull, includingencoding(Encoding, default isnull) andflag(Identifier bit. Default isr) and can also be passed in directlyencoding;
  • Return the value for the contents of the file, if noneencoding, returns the contents of the file as Buffer, if any, parsed according to the passed encoding.

If you have a file named 1.txt and the content is “Hello”, use readFileSync to read it.

Read readFileSync synchronously

const fs = require("fs");

let buf = fs.readFileSync("1.txt");
let data = fs.readFileSync("1.txt"."utf8");

console.log(buf); // <Buffer 48 65 6c 6c 6f>
console.log(data); // HelloCopy the code

(2) Asynchronous reading method readFile

ReadFile is the same as the first two parameters of readFileSync. The last parameter is the callback function, which has two parameters, err and data. This method returns no value and the callback function is executed after reading the file successfully.

1. TXT file:

Read readFile asynchronously

const fs = require("fs");

fs.readFile("1.txt"."utf8", (err, data) => {
    console.log(err); // null
    console.log(data); // Hello
});Copy the code

2. File writing

(1) Synchronous write method writeFileSync

WriteFileSync takes three arguments:

  • The first argument is the path or file descriptor to write to;
  • The second argument is the data to be written, of type String or Buffer.
  • The third parameter is zerooptions, the default value isnull, includingencoding(Encoding, default isutf8),flag(Identifier bit. Default isw) andmode(Permission bit, default is0o666) and can also be passed in directlyencoding.

If you have a file named 2.txt with the content “12345”, now use writeFileSync to write.

Write to writeFileSync synchronously

const fs = require("fs");

fs.writeFileSync("2.txt"."Hello world");
let data = fs.readFileSync("2.txt"."utf8");

console.log(data); // Hello worldCopy the code

(2) Asynchronous write method writeFile

The first three parameters of writeFile are the same as those of writeFileSync. The last parameter is the callback function, which contains one parameter err (error). The callback function is executed after the file writes data successfully.

Write to writeFile asynchronously

const fs = require("fs");

fs.writeFile("2.txt"."Hello world", err => {
    if(! err) { fs.readFile("2.txt"."utf8", (err, data) => { console.log(data); // Hello world }); }});Copy the code

3. File appending

AppendFileSync ()

AppendFileSync takes three arguments:

  • The first argument is the path or file descriptor to write to;
  • The second argument is the data to be written, of type String or Buffer.
  • The third parameter is zerooptions, the default value isnull, includingencoding(Encoding, default isutf8),flag(Identifier bit. Default isa) andmode(Permission bit, default is0o666) and can also be passed in directlyencoding.

If you now have a file named 3.txt with “Hello”, append “world” with appendFileSync.

Synchronize appendFileSync

const fs = require("fs");

fs.appendFileSync("3.txt"." world");
let data = fs.readFileSync("3.txt"."utf8");

console.log(data); // Hello worldCopy the code

(2) asynchronous append write method appendFile

AppendFile is the same as the first three appendFileSync arguments, the last of which is the callback function err (error). The callback function is executed after the file is successfully appended to data.

Asynchronous appendFile is appended

const fs = require("fs");

fs.appendFile("3.txt"." world", err => {
    if(! err) { fs.readFile("3.txt"."utf8", (err, data) => { console.log(data); // Hello world }); }});Copy the code

4. Copy and write files

(1) Synchronous copy writing method copyFileSync

CopyFileSync has two parameters. The first parameter is the path of the source file to be copied, and the second parameter is the path of the target file to be copied. If the target file does not exist, it will be created and copied.

Now copy the contents of 3. TXT above into 4.txt:

Synchronize copy copyFileSync

const fs = require("fs");

fs.copyFileSync("3.txt"."4.txt");
let data = fs.readFileSync("4.txt"."utf8");

console.log(data); // Hello worldCopy the code

(2) Asynchronous copy writing method copyFile

Asynchronous copy writing methods copyFile and copyFileSync have the same first two parameters, and the last parameter is the callback function, which is executed after the copy is completed.

Copy a copyFile asynchronously

const fs = require("fs");

fs.copyFile("3.txt"."4.txt", () => {
    fs.readFile("4.txt"."utf8", (err, data) => {
        console.log(data); // Hello world
    });
});Copy the code

(3) Simulate synchronous and asynchronous copy and write files

Use readFileSync and writeFileSync to simulate synchronous copying and writing files. Use readFile and writeFile to simulate asynchronous copying and writing files.

Analog synchronous copy

const fs = require("fs");

function copy(src, dest) {
    letdata = fs.readFileSync(src); fs.writeFileSync(dest, data); } // copy copy("3.txt"."4.txt");

let data = fs.readFileSync("4.txt"."utf8");
console.log(data); // Hello worldCopy the code
Simulating asynchronous copy

const fs = require("fs");

functionCopy (SRC, dest, cb) {fs.readFile(SRC, (err, data) =>if(! err) fs.writeFile(dest, data, cb); }); } // copy copy("3.txt"."4.txt", () => {
    fs.readFile("4.txt"."utf8", (err, data) => {
        console.log(data); // Hello world
    });
});Copy the code

A high-level approach to file manipulation

1. Open the file open

The open method takes four arguments:

  • Path: indicates the file path.
  • Flag: indicates the flag bit.
  • Mode: indicates the permission bit, default0o666;
  • Callback: A callback function that takes two argumentserr(False) andfd(file descriptor), executed after opening the file.
Open files asynchronously

const fs = require("fs");

fs.open("4.txt"."r", (err, fd) => {
    console.log(fd);
    fs.open("5.txt"."r", (err, fd) => { console.log(fd); }); }); / / 3/4Copy the code

2. Close the file close

The close method takes two arguments. The first argument is the file descriptor fd to close the file, and the second argument is the callback function, which has one argument, err (error), and is executed after the file is closed.

Closing files asynchronously

const fs = require("fs");

fs.open("4.txt"."r", (err, fd) => {
    fs.close(fd, err => {
        console.log("Closed successfully"); }); }); // The shutdown succeededCopy the code

3, Read the file read

The read method differs from readFile in that the Buffer is read multiple times if the file is too large to be read into the cache at once or if the size of the file is unknown. To learn about buffers, see NodeJS — Buffer Interpretation.

The read method takes six arguments:

  • Fd: file descriptor, which must be used firstopenOpen;
  • Buffer: the buffer to be read into;
  • Offset: integer, the initial position to write to Buffer;
  • Length: indicates the length of the file to be read.
  • Position: integer, reading the initial position of the file;
  • Callback: A callback function that takes three argumentserr(false),bytesRead(number of bytes actually read),buffer(the cache object being written), executed after the read execution is complete.

Here read a 6.txt file with the content “hello”.

Asynchronous file reading

const fs = require("fs");
letbuf = Buffer.alloc(6); // Open file fs.open("6.txt"."r", (err, fd) => {// Read file fs.read(fd, buf, 0, 3, 0, (err, bytesRead, buffer) => {console.log(bytesRead); console.log(buffer); Fs. read(fd, buf, 3, 3, (err, bytesRead, buffer) => {console.log(bytesRead); console.log(buffer); console.log(buffer.toString()); }); }); }); // 3 // <Buffer E4 BD A0 00 00 00 00> // 3 / <Buffer E4 BD A0 e5 a5 BD > // HelloCopy the code

4. Synchronize disk cache fsync

The fsync method takes two parameters, the first parameter is the file descriptor fd and the second parameter is the callback function, which has one parameter err (error) and is executed after the disk cache is synchronized.

When using the write method to write data to a file, the disk cache should be synchronized before closing the file for the last write. The fsync method will be used together with the write method later.

5. Write the file write

The write method is different from the writeFile method in that it writes the data in the Buffer to a file. The Buffer functions as a data transfer station. The data source may occupy too much memory or the memory is uncertain, so it cannot be written into the memory at one time.

The write method takes six arguments:

  • Fd: file descriptor, which must be used firstopenOpen;
  • Buffer: A buffer that stores data to be written to a file;
  • Offset: integer, the initial position from which data is read from Buffer;
  • Length: an integer, indicating the number of bytes read from the Buffer.
  • Position: integer, written to the initial position of the file;
  • Callback: A callback function that takes three argumentserr(false),bytesWritten(number of bytes actually written),buffer(read cache object), executed after the write is complete.

Write the two words in the middle of a Buffer to 6.txt. The original content is “hello”.

Select range write

const fs = require("fs");
let buf = Buffer.from("Are you okay?"); // Open file fs.open("6.txt"."r+"Write (fd, buf, 3, 6, 3, (err, bytesWritten, buffer) => {// Synchronize disk cache fs.fsync(fd, buffer) Err => {// Close the fs.close(fd, err => {console.log("Close file"); }); }); }); }); // This is simply used to see if the write succeedsreadThe fs File method. ReadFile ("6.txt"."utf8", (err, data) => { console.log(data); }); / / are you okCopy the code

The above code reads the “ok” in the middle of “are you ok” from Buffer and writes it to the end of “you” in 6.txt. However, the last “OK” is not reserved, indicating that the contents after “you” in the file are emptied before writing.

6. Copy large files

We used readFile and writeFile to implement a copy function. The copy function reads the data of the copied file into memory once and writes it to the target file once, for small files.

If a large file is written at once, it is not practical to write multiple times, so multiple reads and multiple writes are required. Then, we use the above methods to implement a copy function for large files and unknown file sizes.

Large file Copy

/ / copy methodfunctionCopy (SRC, dest, size = 16 * 1024, callback) {// Open fs.open(SRC,"r", (err, readFd) => {// open fs.open(dest,"w", (err, writeFd) => {
            let buf = Buffer.alloc(size);
            letreaded = 0; // Next read file locationletwrited = 0; // Next write file location (function next() {// read fs.read()readFd, buf, 0, size, readed, (err, bytesRead) => { readed += bytesRead; // If none of the contents are available close the fileif(! bytesRead) fs.close(readFd, err => console.log("Close source file")); Write (writeFd, buf, 0, bytesRead, writed, (err, bytesWritten) => {// If there is no content in the cache, and close the file, execute the callbackif(! bytesWritten) { fs.fsync(writeFd, err => { fs.close(writeFd, err =>return! err && callback()); }); } writed += bytesWritten; // Continue reading and writing next(); }); }); }) (); }); }); }Copy the code

In the copy method above, we manually maintain the next read and next write positions. If null is passed to the readed and writed positions, NodeJS automatically maintains these two values for us.

Now we have a file 6.txt with “hello” and an empty file 7.txt. We will write the contents of 6.txt into 7.txt.

Verify large file copies

const fs = require("fs"); // Buffer size const BUFFER_SIZE = 3; // Copy the contents of the file and write copy("6.txt"."7.txt", BUFFER_SIZE, () => {
    fs.readFile("7.txt"."utf8", (err, data) => {// Read the contents of 7.txt after copying console.log(data); / / hello}); });Copy the code

When multiple reads and writes are performed on NodeJS, the size of read and write data is 64K and 16K respectively.

File directory operation method

The following file directory operations have one thing in common, is that the first parameter passed is the file path, such as: A /b/c/d, also divided into synchronous and asynchronous two implementations.

1. View operation permissions of files and directories

(1) View operation permissions synchronously using accessSync

The accessSync method passes a path to a directory and checks whether the directory is readable or writable. No value is returned when permission is granted, and an Error object is raised when permission is not granted or the path is illegal. catch… Do exception catching.

Synchronize view operation rights

const fs = require("fs");

try {
    fs.accessSync("a/b/c");
    console.log("Readable and writable");
} catch (err) {
    console.error("Inaccessible");
}Copy the code

(2) Asynchronous view operation permission method Access

The first parameter of the access method is the path to a directory, and the last parameter is a callback function. The callback function has an parameter err, which is triggered after a permission check. If there is a permission, ERR is null, there is no permission or the path is illegal.

View operation rights asynchronously

const fs = require("fs");

fs.access("a/b/c", err => {
    if (err) {
        console.error("Inaccessible");
    } else {
        console.log("Readable and writable"); }});Copy the code

2. Get the Stats object of the file directory

The Stats object for a file directory stores important information about the file or folder, such as when it was created, when it was last accessed, when it was last modified, how many bytes were used for the article, and how to determine the file type.

(1) Synchronously obtain the Stats object method statSync

The statSync method takes the path of a directory and returns the Stats object for the current directory path. The Stats object is now used to get the size in bytes of the “hello” c.txt file in directory B under directory A.

Get the Stats object synchronously

const fs = require("fs");

let statObj = fs.statSync("a/b/c.txt");
console.log(statObj.size); / / 6Copy the code

(2) Asynchronously obtain the Stats object method stat

The stat method takes the first argument to the directory path and the last argument to the callback function. The callback function takes two arguments, err (error) and a Stats object, and executes after Stats.

Get the Stats object asynchronously

const fs = require("fs");

fs.stat("a/b/c.txt", (err, statObj) => {
    console.log(statObj.size); // 6
});Copy the code

3. Create a file directory

(1) Synchronize the directory creation method mkdirSync

The parameter of the mkdirSync method is the path of a directory and does not return any value. During directory creation, ensure that the file directories before the passed path exist; otherwise, an exception will be thrown.

Create a file directory synchronously

const fs = require("fs"); Fs.mkdirsync (fs.mkdirsync (fs.mkdirsync))"a/b/c");Copy the code

(2) Asynchronous directory creation method mkdir

The first parameter of the mkdir method is the directory path, and the last parameter is the callback function, which has an err (error) parameter. The callback function is executed after the create operation, and also requires that all folders in the preceding part of the path exist.

Create a file directory asynchronously

const fs = require("fs"); // Suppose you already have a folder and a folder b fs.mkdir("a/b/c", err => {
    if(! err) console.log("Created successfully"); }); // Created successfullyCopy the code

4. Read the file directory

(1) Synchronous directory reading method readdirSync

The readdirSync method takes two parameters:

  • The first parameter is the directory path. The directory before the passed path must exist; otherwise, an error will be reported.
  • The second parameter isoptions, includingencodingEncoding, default value isutf8) and can also be passed in directlyencoding;
  • The return value is an array that stores the names of the members in the file directory.

Suppose there is a directory A and a directory B under a, and directory B contains directory C and index.js. Read the file directory structure.

Synchronous directory reading

const fs = require("fs");

let data = fs.readdirSync("a/b"); console.log(data); / / /'c'.'index.js' ]Copy the code

(2) Asynchronous reading directory method readdir

The first two parameters of the readdir method are the same as readdirSync, and the third parameter is a callback function that takes err (error) and data (an array that stores the names of the members in the file directory) and executes after reading the file directory.

The above case is written asynchronously:

Asynchronous directory reading

const fs = require("fs");

fs.readdir("a/b", (err, data) => {
    if(! err) console.log(data); }); / / /'c'.'index.js' ]Copy the code

5. Delete files and directories

Before deleting a file directory, ensure that the path exists and the deleted file directory is empty, that is, no folder or file exists.

(1) Synchronize the directory deletion method rmdirSync

The parameter of rmdirSync is the path of the directory to be deleted. Directory A and directory B under directory A exist. Delete directory B.

Deleting a Directory Synchronously

const fs = require("fs");

fs.rmdirSync("a/b");Copy the code

(2) Asynchronous directory deletion method rmdir

The first parameter of the rmdir method is the same as rmdirSync, and the last parameter is the callback function, which has one parameter err (error) and is executed after the directory deletion operation.

Asynchronously Deleting a Directory

const fs = require("fs");

fs.rmdir("a/b", err => {
    if(! err) console.log("Deleted successfully"); }); // The deletion succeededCopy the code

6. Delete files

(1) unlinkSync is used to delete files

The parameter of unlinkSync is the path of the file to be deleted. The index.js file exists in directory A and directory A. Delete the index.js file.

Deleting files synchronously

const fs = require("fs");

fs.unlinkSync("a/inde.js");Copy the code

(2) Unlink asynchronously deleting files

The first parameter of the unlink method is the same as that of unlinkSync, and the last parameter is the callback function. There is an err parameter in the function, which is executed after deleting the file.

Deleting files asynchronously

const fs = require("fs");

fs.unlink("a/index.js", err => {
    if(! err) console.log("Deleted successfully"); }); // The deletion succeededCopy the code

Implement recursive directory creation

We create a function that takes a path and creates a folder directory level by level.

1. Implementation of synchronization

Recursive deletion of file directories – synchronization

const fs = require("fs");
const path = require("path"); // Create a file directoryfunctionMkPathSync (dirPath) {// path.sep file path separator (MAC is different from window) // Convert to array, such as ['a'.'b'.'c']
    let parts = dirPath.split(path.sep);
    for(leti = 1; i <= parts.length; I++) {// rejoin to a a/b a/b/cletcurrent = parts.slice(0, i).join(path.sep); // If the accessSync path does not exist, throw an error and create a folder try {fs.accesssync (current); } catch(e) { fs.mkdirSync(current); }} // create file directory mkPathSync(path.join("a"."b"."c"));Copy the code

The synchronization code uses the accessSync method to check whether the file path exists, using the try… catch… Error capture, if the path does not exist, an error will be reported, will enter catch to complete the creation of the folder.

2. Asynchronous callback implementation

Recursively delete file directories – asynchronous callback

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

functionMkPathAsync (dirPath, callback) {// Convert to array, such as ['a'.'b'.'c']
    let parts = dirPath.split(path.sep);
    letindex = 1; // Create folder methodfunction next() {
        // 重新拼接成 a a/b a/b/c
        letcurrent = parts.slice(0, index).join(path.sep); index++; // If the path check succeeds, the file directory already exists. Continue to create the next level. // If the path check fails, create the next level.if (err) {
                fs.mkdir(current, next);
            } else{ next(); }}); } next(); } // create a file directory mkPathAsync(path.join("a"."b"."c"), () => {
    console.log("File directory creation completed")}); // The file directory is createdCopy the code

In the above method, the splicing of each directory is not realized through a loop, but through the way of recursive internal function next and the maintenance of index variable. When using access, the success indicates that the file directory already exists, and the recursive creation of the next level is continued. If there is err, it indicates that there is no existence, and the folder is created.

3, asynchronous async/await implementation

The above two methods, synchronous block code, poor performance, asynchronous callback function nesting performance is good, but poor maintenance, we want to have good performance, code readability can be used in NodeJS async/await mode asynchronous programming is popular. To learn more about async/await, see the flow of async/await – the ultimate async/await article.

Asynchronous operations waiting with await in async functions must be converted into promises. Previously, we used the promisify method under util module for transformation. Actually, the principle of promisify method is very simple. We implement the promisify method before implementing recursive file directory creation.

Promisify principle

// Convert an asynchronous method to a Promisefunction promisify(fn) {
    return function(... args) {return new Promise((resolve, reject) => {
            fn.call(null, ...args, err => err ? reject() : resolve());
        });
    }
}Copy the code

The promisify method is implemented with a closure, passing in a function fn that needs to be converted into a Promise, and returning a closure function that returns a Promise instance and synchronously executes fn. Arguments from the closure function and the callback function are passed as arguments to FN via call, which calls reject of the Promise instance if there is an error, and resolve otherwise;

Delete file directories recursively — async/await

const fs = require("fs");
const path = require("path"); // convert the method used in fs to promisify const access = promisify(fs.access); const mkdir = promisify(fs.mkdir); // async/await implementation of recursive create file directory asyncfunctionMkPath (dirPath) {// convert to array, such as ['a'.'b'.'c']
    let parts = dirPath.split(path.sep);

    for(leti = 1; i <= parts.length; I++) {// rejoin to a a/b a/b/cletcurrent = parts.slice(0, i).join(path.sep); // accessSync path does not exist throw an error create folder try {await access(current) in catch; } catch(e) { await mkdir(current); }}} // create file directory mkPath(path.("a"."b"."c")).then(() => {
    console.log("File directory creation completed"); }); // The file directory is createdCopy the code

Using async/await writing method, the code is more like synchronous implementation, but asynchronous execution, so both performance and code readability are taken into account, obvious advantages, when using NodeJS framework Koa 2.x version of the use of asynchronous programming in this way.

conclusion

The synchronous method is characterized by blocking code, resulting in poor performance. The asynchronous code is characterized by nested callback functions. When using FS, asynchronous programming should be used as far as possible to ensure performance. This can be resolved using promises and async/await.


The original source: https://www.pandashen.com