coding
The concept of buffer
Buffer is used to describe memory. Memory is binary and Buffer is hexadecimal
- On the server side, we need something to identify memory, but it can’t be a string, because strings don’t identify images
- Nodes use buffers to identify in-memory data and convert the contents to hexadecimal for display (because hexadecimal is short).
- Base 10 255 => base 2 0b11111111 => hexadecimal 0xFF => Buffer Each byte ranges from 0 to 0xFF
- The JavaScript language itself only has string data types, not binary data types. But when dealing with things like TCP streams or file streams, you must use binary data. Therefore, in Node.js, a Buffer class is defined to create a Buffer dedicated to binary data.
- (UTF-8) 1 Chinese character 3 bytes
- A buffer is similar to an array in that its elements are hexadecimal two-digit numbers ranging from 0 to 255
- Buffer and string conversion in node (garbled)
coding
- Node encodings currently support only UTF8, which takes up different bytes. For UTF8, a Chinese character is three bytes
- A byte is the smallest observable unit
- Bit A byte consists of eight bits (binary)
- Convert any base to base 10: the sum of the current bit * the current bit ^(bit -1) =>
parseInt("1010", 2)
; - To convert an integer to other bases and reverse output => corresponding method:
10.. toString(2)
Base 2 starts with 0B base 8 starts with 0O base 16 starts with 0x
- Decimals are also converted to base 2
- 0.5 in base 10 is 0.1 in base 2 (because 10 => 0.5 20x, 2 => 0.1 20x)
- Decimal Method of converting a decimal to binary: The round by two method converts a decimal to base 2
// 0.1 + 0.2 problem
// The calculation does not add directly, but converts both values to base 2
0.1 * 2 = 0.2 0
0.2 * 2 = 0.4 0
0.4 * 2 = 0.8 0
0.8 * 2 = 1.6 1
0.6 * 2 = 1.2 1
0.1 * 2 = 0.2 0
0.2 * 2 = 0.4 0
0.4 * 2 = 0.8 0
0.8 * 2 = 1.6 1
0.6 * 2 = 1.2 1
// 0.1 to binary is an infinite decimal
// 0.1 is slightly larger than 0.1 when converted to binary storage
// The same goes for 0.2
// So 0.1 + 0.2 will be greater than 0.3
console.log(0.1+0.2) // This is a base conversion problem
// js does not convert decimals to base 2
// why is 0.2+0.2 ok?
// How to solve the accuracy problem?
Copy the code
Coding standards
- ASCII
- A byte can be up to 255, that is, can represent 255 numbers, case and some special symbols used up to 127, a byte to do all the encoding
- GB8030/GBK
- 255 is not enough for Chinese
- From 127 after the compilation of their own, excluding other countries
- unicode
- Organize a code
- UTF8
- Implement your own set of mechanisms using Unicode
- If it’s a character, it still follows ASCII
- Character set
- Node does not support GBK, only UTF8
The common encoding implementation is Base64
Because in some systems only ASCII characters can be used. Base64 is a method used to convert non-ASCII data into ASCII characters.
-
Base64 encoding: Base64 is just an encoding rule without encryption
-
Base64 strings can be placed in links in any path, reducing the number of requests to be sent, but the file size will increase, and the base64 converted file will be 1/3 larger than the previous file
-
Base64 comes from converting every byte to a value less than 64
-
Base64 principle
- A Chinese character has 3 bytes and 24 bits
- To convert 24 bits of a Chinese character into 4 bytes, each byte has 6 bits, but a byte should have 8 bits, so 0 is not enough.
// Base64 method
// Call toString to convert to the specified encoding
// The Chinese character "bead" is 3 bytes, which is 54+g is 4 bytes
const r = Buffer.from('pearl').toString('base64');
Copy the code
Let buffer = buffer. from(" bead "); /* Base64 is a base conversion. Buffer.tostring ("base64") -1. Buffer is a hexadecimal system. To convert hexadecimal to base 2 => toString() (0xe7).toString(2); (0x8f).toString(2); (0xa0).toString(2); // 11100111 10001111 10100000 => convert to 4 bytes, each byte has 6 bits, // 111001 111000 111110 100000 // 00111001 00111000 00111110 00100000 // 00111001 00111000 00111110 00100000 // 00111001 00111000 00111110 00100000 That is, the converted value should not be greater than 64) -2. Convert these values to base 10, ParseInt () parseInt("00111001", 2) or parseInt(0b00111001) //57 parseInt("00111000", 2) //56 parseInt("00111110", 2) //62 parseInt("00100000", 2) //32 visible encoding: Standard base64 only 64 characters (English case, number, +, /) let STR = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; str += "abcdefghijklmnopqrstuvwxyz"; str += "0123456789"; str += "+/"; Console. log(STR [57] + STR [56] + STR [62] + STR [32]) // The base64 encoding is mapped without encryption */
Copy the code
Buffer
- A buffer represents memory. Memory is a “fixed space” that is a fixed amount of memory that cannot be added to it
- If you want to change the size of the buffer, you can intercept the memory if you want to change the size of the buffer. If you want to change the size of the buffer, you need to create a large memory space and copy the data to it
- Alloc is used to declare a buffer or convert a string to a buffer
const buffer1 = Buffer.alloc(5);
// Like an array, but unlike arrays, arrays can be expanded. Buffers cannot be expanded and can be indexed
console.log(buffer[0]); // Prints the decimal number of the byte in the current buffer
// Use hexadecimal
const buffer2 = Buffer.from([0x24.0x25.0x26]);
const buffer3 = Buffer.from('ha ha'); // Corresponding to 6 bytes
Copy the code
- The data obtained in the background are all buffers, including file operations
There are three ways to define buffers
let buf1 = Buffer.alloc(6);
Let buf2 = Buffer. From (' haha ');
Let buf3 = Buffer. The from (,66,67 [65]);
- Define buffers by length
let buffer = Buffer.alloc(6)
Specifies the size of a buffer. The parameter is a number up to 3, where the number refers to bytes
let buffer = Buffer.allocUnsafe(6)
- Define buffers by arrays
Let buffer = buffer. from([1,2,3,4]) automatically converts the decimal number in the array to hexadecimal
The argument is an array, which tells you what to store in a buffer. There are very few scenarios. Only numbers 0-255 can be stored in the array
- String creates buffer – most used
Let buffer = buffer. from(‘ ha ha ‘) => string to buffer
Buffer. length refers to the length of a hexadecimal buffer
Buffer.tostring () => Convert buffer to a string buffer.tostring () does not pass arguments. The default is UTF8
Buffer method
- The buffer function is used to store binary data, and each bit in the buffer represents a byte. If displayed in binary, each byte is displayed as 8 bits. If displayed in hexadecimal, each byte is displayed as two bits. This is why each element of the buffer (each byte) is displayed in hexadecimal
- Whether it’s base 2 or base 16, they represent the same thing
- Common methods of buffer
Buffer. fill fills the contents of the buffer
Buffer.tostring Converts a buffer to a string
Slice Intercepts the desired buffer
Buffer. The buffer copy copies
Buffer. concat Concatenation method for buffers
Buffer. isBuffer Checks whether it is a buffer
var buffer = Buffer.from([1.2.3]);
var newBuffer = buffer.slice(0.1); // Copy the memory address space
newBuffer[0] = 100; // Modify newBuffer, and the buffer also changes
var buf1 = Buffer.from('xi xi');
var buf2 = Buffer.from('ha ha');
var buf = Buffer.allocUnsafe(12);
// Copy buffer
// targetBuffer targetBuffer targetStart start of target sourceStart start of source sourceEnd end of source this.length
// Copy buf1/buf2 to buf
buf1.copy(buf, 0);
buf2.copy(buf, 6);
console.log(buf, buf.toString());
// If the buffer is too long, you need to calculate the location of the copy, and you cannot change the size of the buffer.
// So concat is more convenient. It automatically creates the sum of the two buffers to be concatenated and then concatenates them.
/ / connection buffer
Buffer.concat([buf1, buf2]).toString();
Buffer.prototype.MyCopy = function (
targetBuffer,
targetStart,
sourceStart = 0,
sourceEnd = this.length
) {
for (let i = sourceStart; i < sourceEnd; i++) {
targetBuffer[targetStart++] = this[i]; }}; Buffer.MyConcat =function (list, totalLength) {
// Determine whether the length is passed, if passed, use the total length
if (typeof totalLength === 'undefined') {
totalLength = list.reduce((prev, next) = > prev + next.length, 0);
}
// Create a buffer of this size by length, buffer.alloc (len)
let buffer = Buffer.alloc(totalLength);
// Recirculate the list to copy each item to the large buffer buf.copy
let offset = 0;
list.forEach((buff) = > {
if(! Buffer.isBuffer(buff))return;
buff.copy(buffer, offset);
offset += buff.length;
});
// If the length is too long, fill or use slice to intercept the valid length
// Return a new buffer
return buffer.slice(0, offset);
};
Copy the code
fs
- I/O Input Output Read files => Write operations (memory as a reference)
- When reading, the default encoding is buffer. If the file does not exist, an error is reported
- ToString (‘utf8’); toString(‘utf8’);
- The concept of reading is to read the contents of a file into memory, while writing is to read the contents of memory and write into a file
File status determination — fs.stat
fs.stat('1.txt'.function (err, stats) {
if (err)
// File does not exist
stats.isFile(); // Whether it is a file
stats.isDirectory(); // Whether it is a folder
});
Copy the code
Create a folder — fs.mkdir
Create folder D only when a/ B/C exists
fs.mkdir('a/b/c/d'.function (err) {});
// Create folders recursively
function makep(url, cb) {
let urlArr = url.split('/');
let index = 0;
function make(p) {
if (urlArr.length < index) return; // Terminate the loop
// See if it exists before creating it. If not, create it
fs.stat(p, function (err) {
if (err) {
// File does not exist
fs.mkdir(p, function (err) {
index++;
make(urlArr.slice(0, index + 1).join('/'));
});
} else {
// If so, skip to the next creation
make(urlArr.slice(0, ++index + 1).join('/')); }}); } make(urlArr[index]); } makep('a/b/c/d'.function (err) {
console.log('Created successfully');
});
Copy the code
Read folder
fs.readdir('a'.function(err,dirs){}) // Only one layer can be read
Copy the code
Delete folders
fs.rmdir("a".function(err){}) // Delete the folder, do not delete the folder when there is content under it
Copy the code
Self-implementation Deletes folders
- Asynchronous deletion of multilayer directories
- Serial delete
- Concurrent delete delete subfolders delete and then delete yourself
- Breadth traversal and then reverse delete
// Serial delete
function rmdir(dir, cb) {
fs.stat(dir, function(err, stat) {
if(stat.isDirectory()) {
fs.readdir(dir, function(err, dirs) {
dirs = dirs.map(item= > path.join(dir, item));
let index = 0;
function step() {
if(index === dirs.length) return fs.rmdir(dir, cb);
rmdir(dirs[index++], step)
}
step()
})
}else{
fs.unlink(dir, cb)
}
})
}
// Serial promise
const fs = require("fs").promises;
const path = require("path");
async function rmdir(dir) {
const statObj = await fs.stat(dir);
if(statObj.isDirectory()) {
let dirs = await fs.readdir(dir);
dirs = dirs.map(item= > path.join(dir, item));
for(let i = 0; i<dirs.length; i++) {
await rmdir(dirs[i]);
}
await fs.rmdir(dir);
}else{
return fs.unlink(dir);
}
}
rmdir("aaa").then(() = > {
console.log("success zhuzhu")})// Concurrent delete
function rmdir(dir, cb) {
fs.stat(dir, function(err, statObj) {
if(stat.isDirectory()) {
fs.readdir(dir, function(err, dirs) {
dirs = dirs.map(item= > path.join(dir, item));
if(! dirs.length) {return fs.rmdir(dir, cb);
}
let v = 0;
function done() {
if(++v === dirs.length) {
return fs.rmdir(dir, cb)
}
}
for(let i = 0; i < dirs.length; i++) { // Execute concurrently
rmdir(dirs[i], done)
}
})
}else{
fs.unlink(dir, cb)
}
})
}
// Make a promise
const fs = require("fs").promises;
const path = require("path");
async function rmdir(dir) {
const statObj = await fs.stat(dir);
if(statObj.isDerectory()) {
let dirs = await fs.readdir(dir);
await Promise.all(dirs.map(item= > rmdir(path.join(dir, item))));
await fs.rmdir(dir);
}else{
returnfs.unlink(dir); }}// serial width traversal
const fs = require("fs").promises;
const path = require("path");
async function rmdir(dir) {
let stack = [dir];
let index = 0;
let currentNode;
while(currentNode = stack[index++]){ // width traversal, put all folders and files
let statObj = await fs.stat(currentNode);
if(statObj.isDirectory()) {
let dirs = await fs.readdir(currentNode);
dirs = dirs.map(item= >path.join(currentNode, item)); stack = [...stack, ...dirs]; }}// Delete in reverse order
let len = stack.length;
while(len--) {
let stat = await fs.stat(stack[len]);
if(stat.isDirectory()) {
await fs.rmdir(stack[len])
}else{
await fs.unlink(stack[len])
}
}
}
rmdir("aaa").then(() = > {
console.log("success zhuzhu")})Copy the code
Read the file
- To read files files must have contents that cannot be read through /, which represents the root directory
- The default type to read is buffer
let result = fs.readFileSync('log.js');
// There are two ways to convert the result format:
result.toString();
fs.readFileSync('log.js'.'utf8');
Copy the code
Written to the file
- The read type is buffer and the write type is UTF8
- The read file must exist. If the file does not exist, it will be automatically created and its contents will be overwritten
- The toString method is called for the second argument (the contents of the file) by default.
fs.writeFile('1.txt', document content,(err) = > {});
fs.writeFileSync('1.txt', document content);Copy the code
Reading and writing files (copying)
// Implement synchronous/asynchronous file reading and writing functions
let fs = require("fs");
fucntion copySync(source, target) { // Synchronize readFileSync + writeFileSync
let result = fs.readFileSync(source, "utf8");
fs.writeFileSync(target, result);
}
function copy(source, target, callback){ // Asynchronous readFile + writeFile
fs.readFile(source, function(err, data) {
if(err) return callback(err)
fs.writeFile(target, data, callback)
})
}
copy("1.txt"."2.txt".function(res) {
console.log(res);
})
Copy the code
- The above method can only be written after reading, which is suitable for small files. Large files using this method will flood the available memory
- Read and write
// This code block can be ignored
// Implement reading and writing three at a time: 123456789
const fs = require('fs');
const path = require('path');
let buf = Buffer.alloc(3);
fs.open(path.resolve(__dirname, 'a.txt'), 'r'.function (err, fd) {
// fd:file descriptor is a number type, which should be closed when the file descriptor is used
console.log(fd, 'fd');
// read a.txt writes the read contents to position 0 of the buffer, three times, starting from position 6 of the file
fs.read(fd, buf, 0.3.6.function (err, bytesRead) {
// bytesRead is the actual number read
console.log(bytesRead, 'bytesRead', buf);
// Open and w will empty the file and always write the first 3 bytes
fs.open(path.resolve(__dirname, 'b.txt'), 'w'.function (err, wfd) {
// Read buffer data from 0 to the 0th position of the three written files
fs.write(wfd, buf, 0.3.0.function (err, written) {
console.log(written);
// Internal recursion.....
// Close the file
fs.close(fd, () = > {});
fs.close(wfd, () = > {});
});
});
});
});
Copy the code
- W Write operation R read operation A append operation R + Write operation w+ Read operation
- Permissions: 3 groups (permissions of the current user Permissions of the user group Permissions of others) RWX combination (read, write, and execute) 421 => 7 => Highest permissions 777 (base 8)
- Fs.open (source,”r”,0o666, function(){}
Flow based on file system operations
- The following implementation reads and writes are coupled together
- Fs is a file stream, a self-implemented stream in file operations. File streams inherit from stream, and the underlying implementation uses fs.open
fs.read fs.write…
// Copy three bytes at a time
const fs = reuqire('fs');
function copy(source, target, cb) {
const BUFFER_SIZE = 3;
const buffer = Buffer.alloc(BUFFER_SIZE);
const r_offset = 0;
const w_offset = 0;
// Write some data while read some data
fs.open(source, 'r'.function (err, rfd) {
fs.open(target, 'w'.function (err, wfd) {
// The callback method is used recursively
// The synchronization code can use a while loop
function next() {
fs.read(rfd,buffer,0,BUFFER_SIZE,r_offset,function (err, bytesRead) {
if (err) return cb(err);
if (bytesRead) {
fs.write(wfd,buffer,0,bytesRead,w_offset,function (err, written){ r_offset += bytesRead; w_offset += written; next(); }); }else {
fs.close(rfd, () = > {});
fs.close(wfd, () = >{}); cb(); }}); } next(); }); }); } copy('./a.txt'.'./b.txt'.function (err) {});
Copy the code
To decouple read and write, adopt a publish-subscribe model and have readable/writable streams
A readable stream
- If a byte in buffer displays 30 => hexadecimal 30 to 10:48 => find the corresponding in THE ASCII code
- The concept of flow has nothing to do with FS
- Fs extends a file read/write method based on the stream module
- Fs.createreadstream () creates a readable stream object rs, which listens for events
- If you do not listen for the “data” event, it is in non-flowing mode, equivalent to buying a hose, but the water will not flow out
- When listening to the “data” event, from non-flowing mode => flowing mode, the data event callback function will be triggered repeatedly, and the highWaterMark value will be fetched each time
- The end event is raised when the file is finished reading
- So streams are event-based
- Readable stream has method: the data/end/error/resume/pause, as long as you have these methods is called a readable stream
- Open and close are methods unique to file streams
Use of readable streams
const fs = require('fs');
// Returns a readable stream object
const rs = fs.createReadStream('./a.txt', {
// Create a readable stream without passing its own parameters
flags: 'r'.// r reads for fs.open
encoding: null.// The buffer is read by default
autoClose: true.// Close the stream fs.close after reading
emitClose: true.// Trigger a close event after reading
start: 0.highWaterMark: 3.// The default value is 64 x 1024 bytes
});
// If you do not listen for the "data" event, it is in non-flowing mode, equivalent to buying a pipe, but the water does not flow out
// It will listen to the user, bind the data event will trigger the corresponding callback, constantly firing
// open/close is unique to the file stream
rs.on('open'.function (fd) {
// Open file event
console.log(fd);
});
rs.on('data'.function (chunk) { // If data is bound, data will be triggered continuouslyEvents pass internal data out// Read file event
console.log(chunk);
rs.pause(); // Pause does not continue to fire data events
});
rs.on('end'.function () {
// File read completion event
console.log('end');
});
rs.on('close'.function () {
// Close the file event
console.log('close');
});
rs.on('error'.function (err) {
console.log(err, 'error');
});
setInterval(() = > {
rs.resume(); // Restore the event that triggered data
}, 1000);
Copy the code
Implementation of readable streams
The idea here is worth learning
- The open method is asynchronous, and the implementation calls the read method to read the file when the open execution is complete
const fs = require('fs');
const EventEmitter = require('events');
class ReadStream extends EventEmitter {
constructor(path, options = {}) {
super(a);this.path = path;
this.flags = options.flags || 'r';
this.encoding = options.encoding || null;
this.autoClose = options.autoClose || true;
this.start = options.start || 0;
this.end = options.end;
this.highWaterMark = options.highWaterMark || 64 * 1024;
this.open(); // File open operation Note that this method is asynchronous
// Note that the user listens for the data event to read the file
// this.read();
// EventEmitter emits newListeners internally as long as the method is bound
// This will allow you to monitor which events the user is bound to
// newListener execution is triggered when the binding method name is not newListener
this.on('newListener'.function (type) {
console.log(type); // The newListener event is raised when an event is bound
if (type === 'data') {
this.read(); }}); }open() {
fs.open(this.path, this.flags, (err, fd) = > {
if (err) {
return this.destroy(err);
}
this.fd = fd; // Used by the read method
this.emit('open', fd); // Trigger the open event, when the file is opened, you can retrieve the fd
});
}
read() {
// The read method takes the fd
// The fd is not available until the open callback is executed
if (typeof this.fd ! = ='number') {
// return this.once("open", (fd) => {
// console.log(fd);
// })
return this.once('open'.() = > this.read());
}
console.log(this.fd); // You can get fd
}
destroy(err) {
if (err) {
this.emit('error', err); }}}module.exports = ReadStream;
Copy the code
// Complete implementation
const fs = require('fs');
const EventEmitter = require('events');
class ReadStream extends EventEmitter {
constructor(path, options = {}) {
super(a);this.path = path;
this.flags = options.flags || 'r';
this.encoding = options.encoding || null;
this.autoClose = options.autoClose || true;
this.start = options.start || 0;
this.end = options.end;
this.highWaterMark = options.highWaterMark || 64 * 1024;
/ / the offset
this.offset = this.start;
/ / switch
// false Non-flowing mode, the contents of the file have not been read. The subsequent pause and resume would update this flowing property
this.flowing = false;
this.open(); // File open operation Note that this method is asynchronous
// Note that the user listens for the data event to read the file
// this.read();
// EventEmitter emits newListeners internally as long as the method is bound
// This will allow you to monitor which events the user is bound to
// newListener execution is triggered when the binding method name is not newListener
this.on('newListener'.function (type) {
console.log(type); // The newListener event is raised when an event is bound
if (type === 'data') {
this.flowing = true;
this.read(); }}); }open() {
fs.open(this.path, this.flags, (err, fd) = > {
if (err) {
return this.destroy(err);
}
this.fd = fd; // Used by the read method
this.emit('open', fd); // Trigger the open event, when the file is opened, you can retrieve the fd
});
}
read() {
// The read method takes the fd
Fd this.open is asynchronous
if (typeof this.fd ! = ='number') {
// return this.once("open", (fd) => {
// console.log(fd);
// })
return this.once('open'.() = > this.read());
}
console.log(this.fd); // You can get fd
const buffer = Buffer.alloc(this.highWaterMark);
// Read the contents of the file, this.highwatermark one at a time
let howMuchToRead = this.end
? Math.min(this.end - this.offset + 1.this.highWaterMark)
: this.highWaterMark;
fs.read(this.fd,buffer,0,howMuchToRead,this.offset,(err, bytesRead) = > {
if (bytesRead) {
this.offset += bytesRead;
this.emit('data', buffer.slice(0, bytesRead));
if (flowing) {
this.read(); / / to read}}else {
this.emit('end');
this.destroy(); }}); }destroy(err) {
if (err) {
this.emit('error', err);
}
if (this.autoClose) {
fs.close(this.fd, () = > this.emit('close')); }}pause() {
this.flowing = false;
}
resume() {
if (!this.flowing) {
this.flowing = true;
this.read(); }}}module.exports = ReadStream;
Copy the code
Summary of implementation process:
The constructor sets the default parameters, opens the file directly, and listens for the user’s events and starts reading if the user is bound to a data event
However, if the file is not open during the reading process, binding an open event and calling read itself after the file is opened is equivalent to delaying the read method
When the file is opened, open will be called, and read will be called, and fd will be ready to read the file
Each read file is put into buffer and thrown out
If it is in flow mode, the next read continues until the read is complete, triggering the end/close event
Streams can be written
- fs.createWriteStream(“./b.txt”)
- ws.on(‘open’)
- ws.write
- ws.on(‘drain’)
- ws.end
const fs = require('fs');
const ws = fs.createWriteStream('./b.txt', {
flags: 'w'.encoding: 'utf8'.autoClose: true.start: 0.highWaterMark: 3.// The highWaterMark for writable streams is different from the highWaterMark for readable streams, indicating how much memory is expected to be used to write
});
ws.on('open'.(fd) = > {
console.log('open', fd);
})
let flag = ws.write('1');
flag = ws.write('2');
flag = ws.write('345');
ws.end(); // Write +end
// Since the write method is asynchronous, if multiple write methods operate on the same file at the same time, there will be an error.
// Write the first time, queue the rest of the queue, put it on the stack, finish first, clear the cache,
// If the cache size is too large, memory will be wasted, so an expected value will be set to control, and the write method will not be called after the expected value is reached
let index = 0;
function write(){ // Will waste memory, only the first write is real, the subsequent are put into the cache
while(index ! =5 ){
ws.write(index++ + ' ')
}
}
write();
let index = 0;
function write(){
let flag = true;
while(index ! =5 && flag){
flag = ws.write(index++ + ' ')
}
}
write();
ws.on('drain'.function(){ / / to dry up. This alarm is triggered when the expected data has been cleared and written to a file
write();
console.log('drain');
})
setTimeout(() = >{
ws.end('Can write content'); Ws.write ()+fs.close
})
Copy the code
Implementation of writable streams
const fs = require('fs');
const EventEmitter = require('events');
class WriteStream extends EventEmitter {
constructor(path, options = {}) {
super(a);this.path = path;
this.flags = options.flags || 'w';
this.encoding = options.encoding || null;
this.autoClose = options.autoClose || true;
this.emitClose = options.emitClose || true;
this.start = options.start || 0;
this.highWaterMark = options.highWaterMark || 16 * 1024;
this.open();
// Customize the attributes written to the stream
this.len = 0; // Record the total number of writes
this.offset = this.start; // Write the offset
this.cache=[]; // Cache write operations
this.needDrain = false; // Whether to fire the drain event
this.writing = false; // Indicates whether writing is in progress. If writing is in progress, put it in the cache
}
open() {
fs.open(this.path, this.flags, (err, fd) = > {
if (err) {
return this.destroy(err);
}
this.fd = fd; // Used by the read method
this.emit('open', fd); // Trigger the open event, when the file is opened, you can retrieve the fd
});
}
write(chunk, encoding=null, cb=() => {}) {
chunk = Buffer.isBuffer(chunk)? chunk : Buffer.from(chunk);
this.len += chunk.length;
this.needDrain = this.len >= this.highWaterMark;
let oldCb = cb;
After each successful write, the successful callback is called and another callback is fetched from the cache to continue writing
// Until the cache is empty
cb = () = > {
oldCb();
this.clearBuffer();
}
if(this.writing) {
// Put it in the cache
this.cache.push({chunk, encoding, cb});
}else{
// Write to the file
this.writing = true;
this._write(chunk, encoding, cb);
}
return !this.needDrain;
}
_write(chunk, encoding, cb) {
if(typeof this.fd ! = ='number') {
return this.once('open'.() = > this._write(chunk, encoding, cb));
}
fs.write(this.fd, chunk, 0, chunk.length, this.offset, (err,writen) = > {
this.offset += writen;
this.len -= writen; cb(); })}clearBuffer() {
// Clear the cache after writing
let obj = this.cache.shift();// The bottom layer uses linked lists to optimize queues
if(obj) {
this._write(obj.chunk, obj.encoding, obj.cb);
}else{
this.writing = false;
if(this.needDrain) {
this.needDrain = false;
this.emit("drain"); }}}}module.exports = WriteStream;
Copy the code
pipe
- readSteam.pipe(writeStream);
- This method is asynchronous, reads and writes point by point, and supports large file operations
const fs = reuqire("fs");
const rs = fs.createReadStream("1.txt", {highWaterMark: 3});
const ws = fs.createWriteStream("2.txt", {highWaterMark: 1});
rs.on("data".function(chunk) {
console.log(chunk);
let flag = ws.write(chunk);
if(! flag) { rs.pause(); } }) ws.on("drain".function() {
console.log("drain");
rs.resume();
})
// A simple way to implement the above function
rs.pipe(ws);
// So there is a method called pipe in ReadStream, based on the above code
Copy the code
The list
- Unidirectional list to achieve the increase, delete, change and check function
- Reverse a one-way linked list
class Node{
constructor(element, next) {
this.element = element;
this.next = next; }}class LinkedList {
constructor() {
this.head = null;
this.size = 0; // The total length of the list
}
_getNode(index) {
let head = this.head;
for(let i = 0; i <= index; i++) {
head = head.next;
}
return head;
}
/ / to add
add(index, element) { // Index can be passed without index
if(arguments.length === 1) {
element = index;
index = this.size;
}
if(index == 0) { // Modify the header of the list
let oldHead = this.head;
this.head = new Node(element, oldHead);
}else {
let preNode = this._getNode(index - 1); // Always find the previous node of the current index
preNode.next = new Node(element, preNode.next);
}
this.size++;
}
/ / delete
remove(index) {
let removeNode;
if(index == 0) {
removeNode = this.head;
if(removeNode == null) return;
this.head = this.head.next;
}else{
let preNode = this._getNode(index - 1);
if(! preNode)return;
removeNode = preNode.next;
preNode.next = preNode.next.next;
}
this.size --;
return removeNode;
}
/ / change
update(index, element) {
let node = this._getNode(index);
node.element = element;
}
/ / check
getNode(index) {
return this._getNode(index);
}
// Reverse the one-way list recursion
reverse() {
function n(head) {
if(head == null || head.next == null) return head;
let newHead = n(head.next); // The new head becomes the next
head.next.next = head.next; // Let the next next point to the old head
head.next = null; // The next pointer to the old header is null
return newHead;
}
n(this.head);
}
// Reverse unidirectional lists to be non-recursive
reverse(){
let head = this.head;
if(head == null || head.next == null) return head;
let newHead = null;
while(head) {
let n = head.next;
head.next = newHead;
newHead = head;
head = n;
}
returnnewHead; }}let ll = new LinkedList();
ll.add(0.1);
ll.add(0.2);
ll.add(0.3);
console.dir(ll, {depth:1000});
Copy the code
- Use linked lists to create queues
class Queue{
constructor() {
this.ll = new LinkedList();
}
add(element) {
this.ll.add(element)
}
peak() {
return this.ll.remove(0); }}Copy the code
Binary search tree
- Create a binary search tree
class Node {
constructor(element, parent) {
this.element = element;
this.parent = perent;
this.left = null;
this.right = null; }}class BST {
constructor() {
this.root = null;
}
add(element) {
if(this.root == null) {
this.root = new Node(element, null);
return;
}else{
let current = this.root;
let parent = null;
let compare = true;
while(current) {
parent = current;
compare = current.element - element > 0;
if(compare) {
current = current.left;
}else{ current = current.right; }}let newNode = new Node(element, parent);
if(compare) {
parent.left = newNode;
}else{ parent.right = newNode; }}}}Copy the code
- A more general way to compare
- Tree traversal
- Sequential traversal – visits the root node first, then the left subtree, then the right subtree
- Traversal a tree, encountered a node on the processing of a node can be used in advance traversal
- Middle order traversal — left subtree root node right subtree
The tree can be processed either in ascending or descending order in a middle-order traversal
- In order
- According to the binary search tree property: traversal results are in ascending order
- Back-order traversal — left subtree right subtree root node
- You deal with your son before you deal with yourself
- Sequence traversal
- Breadth-first traversal
- Depth-first traversal
- Sequential traversal – visits the root node first, then the left subtree, then the right subtree
- Invert the binary tree
- The four traversal implementations below can all implement inverted binary trees
class Node {
constructor(element, parent) {
this.element = element;
this.parent = perent;
this.left = null;
this.right = null; }}class BST {
constructor(compare) {
this.root = null;
let internalCompare = this.compare;
this.compare = compare || internalCompare;
}
compare(current, element) {
return current - element > 0;
}
add(element) {
if(this.root == null) {
this.root = new Node(element, null);
return;
}else{
let current = this.root;
let parent = null;
let compare = true;
while(current) {
parent = current;
compare = this.compare(current.element, element);
if(compare) {
current = current.left;
}else{ current = current.right; }}let newNode = new Node(element, parent);
if(compare) {
parent.left = newNode;
}else{ parent.right = newNode; }}}// order traversal first
preorderTraversal(cb){
const traversal = node= > {
if(node == null) return;
cb(node);
traversal(node.left);
traversal(node.right);
}
traversal(this.root);
}
// middle order traversal
inorderTraversal(cb){
const traversal = node= > {
if(node == null) return;
traversal(node.left);
cb(node);
traversal(node.right);
}
traversal(this.root);
}
// after the sequence traversal
postorderTraversal(cb){
const traversal = node= > {
if(node == null) return;
traversal(node.left);
traversal(node.right);
cb(node);
}
traversal(this.root);
}
// sequence traversal
levelorderTraversal(cb) {
let stack = [this.root];
let i = 0;
let currentNode;
while(currentNode = stack[i++]) {
cb(currentNode);
if(currentNode.left) {
stack.push(current.left);
}
if(currentNode.right) { stack.push(currentNode.right); }}}// Invert the binary tree
invertTree(cb) {
const traversal = (node) = > {
if(node == null) return;
let r = node.right;
node.right = nodee.left;
nodee.left = r;
traversal(node.left);
traversal(node.right);
cb(node);
}
traversal(this.root)
return this.root; }}let bst = new BST((a,b) = > {
return a.age - b.age > 0
});
let arr = [{age: 15}, {age: 18}, {age: 9}, {age: 12}, {age: 23}];
arr.forEach(item= > {
bst.add(item);
})
bst.preorderTraversal((node) = > {
console.log(node.element)
})
Copy the code