一、Buffer
1、简介
Buffer 是Node.js提供的一个全局基础对象,无需引用,可直接使用,本质上是一块固定大小的原始内存空间,专门用于处理二进制数据。通常被称为”缓冲区“。其类似于一个整数数组(元素值在0~255之间),表示固定长度的字节序列,每个元素表示一个字节(8位)。并且 Buffer 的大小在创建时就决定好了,一旦创建,其大小就不能改变。
Buffer 是为了解决 JS 处理二进制数据效率低的问题而出现的,因此其内存分配不由 V8 引擎管理,而是直接在 Node.js 的C++层进行操作,在处理大量数据时,性能优势明显。
2、常见用途
- 原始内存操作:直接操作内存,性能极好。
- 二进制数据处理:文件读写、网络通信、加密/解密、图像/音视频等媒体文件处理等等。
- 编码转换:充当字符串和各种二进制编码之间转换的桥梁,可以根据指定的字符编码(utf8、base64、ascii等)将目标数据在二进制数据和字符串之间进行转换。
3、创建方法
① Buffer.alloc(size)
创建 size 字节长度的 Buffer,相当于申请了 size 字节的内存空间,并将每个字节的值都初始化为0。
② Buffer.allocUnsafe(size)
创建 size 字节长度的 Buffer,相当于申请了 size 字节的内存空间,但并不对空间内的字节进行初始化。性能要比 Buffer.alloc() 方法要好,但内存空间内可能包含旧数据,谨慎使用。使用时,最好立即用数据完全填充,以确保安全。
③ Buffer.from(data)
根据指定数据 data 创建Buffer,data支持多种类型,常见的有:String、Array、Buffer。
- String类型:该方法可以接收一个可选参数 encoding,用于指定字符串的字符编码,默认值为 utf8,然后该方法会创建一个包含data编码后内容的 Buffer。
- Array类型:数组元素为0-255的整数,则该方法会创建一个包含data中字节数据副本的Buffer。
- Buffer类型:该方法会创建一个包含 data 数据副本的新Buffer。
// 1. 指定长度创建(初始化为0)
const buf2 = Buffer.alloc(10); // 长度为10的Buffer,内容全为0
// 2. 未初始化的Buffer(性能更好,但存在旧数据)
const buf3 = Buffer.allocUnsafe(10);
// 3. 从字符串创建
const buf1 = Buffer.from('hello', 'utf8'); // <Buffer 68 65 6c 6c 6f>
// 4. 从数组创建(数组元素为0-255的整数)
const buf4 = Buffer.from([0x68, 0x65, 0x6c, 0x6c, 0x6f]);
4、常用属性
① length
该属性用于获取 Buffer 的内存空间大小,即多少个字节,是一个固定数值,不会改变。
const buf = Buffer.alloc(100);
console.log(buf.length); // 100
5、常用方法
① Buffer.isBuffer(data)
该方法用于判断一个值是否为Buffer类型。
该方法返回值为一个布尔值。
const buf = Buffer.alloc(100);
console.log(Buffer.isBuffer(buf)); // true
② buf.write(string[, offset[, length]][, encoding])
该方法用于向 Buffer类型的 buf 中写入数据,参数 string 表示要写入的字符串;可选参数 offset 表示开始写入数据之前要跳过的字节数,默认为0;可选参数 length 表示要写入的最大字节数量,默认为 buf.length-offset;可选参数 encoding 表示参数 string的字符编码,默认为 utf8。
该方法返回值为一个数值,表示写入的字节数量。
const buf = Buffer.alloc(10);
const length = buffer.write('abcd');
除了该方法之外,Buffer 还可以通过buf[index]
的形式,直接对数据进行读写,以及很多其他写入方法,比如:writeUInt8()、writeString()等等,具体可查阅官方文档。
const buf = Buffer.alloc(10);
console.log(buf[2]); // 0
buf[2] = 11;
console.log(buf[2]); // 11
③ buf.toString([encoding[, start[, end]]])
该方法用于将 Buffer类型的 buf 转换为字符串,可选参数 encoding 表示要使用的字符编码规则,也就是将数据转换为什么字符编码的字符串,默认值为 utf8;可选参数 start 表示从 buf 的哪个字节开始转换,默认值为 0;可选参数 end 表示转换到 buf 的哪个字节停止,默认值为 buf.length。
该方法返回值为字符串,表示转换为的字符串。
const buf = Buffer.from('hello');
console.log(buf.toString()); // hello
④ Buffer.concat(list)
该方法用于按照先后顺序拼接多个Buffer,参数值是一个数组,数组元素为要拼接的Buffer。
该方法返回值是一个新 Buffer,是 list 中 Buffer 拼接后的结果。
const buf1 = Buffer.from('hello');
const buf2 = Buffer.from(' world');
const combined = Buffer.concat([buf1, buf2]);
console.log(combined.toString()); // "hello world"
⑤ buf.fill(value[, offset[, end]][, encoding])
该方法使用指定的 value 填充 Buffer类型的 buf,参数 value 表示用于填充的值;可选参数 offset 表示开始填充之前要跳过的字节数,默认为0;可选参数 end 表示填充到 buf 的哪个字节停止,默认值为 buf.length;可选参数 encoding 表示如果 value 是字符串,则设置对应的字节编码,默认值为 utf8。
该方法的返回值是一个Buffer,表示对 buf 的引用。
const buf = Buffer.allocUnsafe(100);
// 将buffer中的空间全部填充为0
buf.fill(0);
⑥ buf.copy(target[, targetStart[, sourceStart[, sourceEnd]]])
该方法用复制 Buffer类型的 buf 中的数据到 Buffer类型 target 中,参数 target 表示要复制到的 Buffer 区域;可选参数 targetStart 表示从 target 内的哪个字节开始写入,默认值为0;可选参数 sourceStart 表示从 buf 哪个字节开始复制,默认值为0;可选参数 sourceEnd 表示复制到 buf 的哪个字节停止,默认值为 buf.length。buf 与target 中的数据相互独立,互不影响。
该方法的返回值是一个数值,表示复制的字节数量。
// 创建一个Buffer
const buf = Buffer.from('hello');
// 创建新的Buffer
const copybuf = Buffer.alloc(buf.length);
// 复制
buf.copy(copybuf);
// 修改copybuf不会影响buf
copybuf[0] = 0x48;
// 输出
console.log(buf.toString()); // "hello"
console.log(copybuf.toString()); // "Hello"
⑦ buf.slice([start[, end]])
该方法用于截取 Buffer类型的 buf 中的部分数据,可选参数 start 表示从 buf 的哪个字节开始截取,默认值为0;可选参数 end 表示截取到 buf 的哪个字节,默认值位 buf.length。
该方法的返回值是一个新的Buffer,但是其内部数据与 buf 数据指向同一块内存,也就是说两者之间数据相同,会相互影响。
⑧ includes()、equals()、indexOf()等其他诸多方法
。。。
6、编码与解码
此处的编码与解码指的是 Buffer 与 字符串相互转换的过程。编码是指按照指定的字符编码规则,将字符串转换为Buffer的过程,通常使用 Buffer.from() 方法,可指定字符编码,默认使用 utf8 ;解码是指按照指定的字符编码规则,将Buffer数据还原成字符串的过程,通常使用 buffer.toString() 方法,可指定字符编码,默认值为 utf8。
常见的字符编码有:utf8、base64、ascii、hex(十六进制字符串) 等等。
const str = "hello";
// 指定字符集 将指定字符串编码为buffer 默认使用utf8
const buf = Buffer.from(str);
// 可将 Buffer 按照base64解码为字符串
console.log(buf.toString("base64")); // "aGVsbG8="
// 也可将 Buffer 按照utf8解码为字符串
console.log(buf.toString()); // "hello"
二、path 模块
1、简介
path 模块是是Node.js最常用的内置模块之一,专门用于处理文件和目录路径,提供了一系列跨平台的属性和方法,可以实现在不同操作系统上以相同的方式处理路径,解决了不同操作系统路径格式不同的问题。
Node.js的内置模块是Node.js提供的一系列核心功能库,实现了访问底层系统功能的标准接口,无需额外安装即可直接导入使用。内置模块有很多,常见的有:fs模块、path模块、http模块、events模块、stream模块等。
2、导入模块
// CommonJS
const path = require('path');
// ES Modules
import path from "path"
3、常用属性
① path.sep
该属性用于获取当前操作系统使用的路径分隔符,Windows系统是反斜杠 \
,POSIX系统(macOS和Linux)是斜杠 /
。
import path from "path"
console.log(path.sep);
② path.delimiter
该属性用于获取当前操作系统使用的环境变量分隔符,Windows系统是分号 ;
,POSIX系统(macOS和Linux)是冒号 :
。
import path from "path"
console.log(path.delimiter);
③ 强制指定平台
模块提供了path.win32
和 path.posix
两个属性,属性值是一个对象,里面存储了对应的平台的路径方法,用于强制指定当前平台。
import path from "path"
// 指定平台
const curPath = path.win32;
// 使用新的path对象进行路径操作
console.log(curPath.sep); // "\"
4、常用方法
① path.normalize(path)
该方法用于规范化路径,解析处理路径path(String类型)中的..
(上层目录)和.
(当前目录)以及多余的分隔符,返回结果为规范后的路径。
import path from "path"
// 在POSIX系统中
const resPath = path.normalize('/foo/bar//baz/asdf/quux/..');
console.log(resPath); // "/foo/bar/baz/asdf"
// Window系统中
const resPath = path.normalize('C:\\temp\\\\foo\\bar\\..\\');
console.log(resPath); // "C:\\temp\\foo\\"
② path.join([…paths])
该方法用于将多个路径片段从左到右依次拼接成一个完整的路径,并进行规范化处理,而且会根据当前平台自动使用对应的路径分隔符,避免了手动拼接可能出现的问题。
路径片段必须都是String类型,其中可以包含..
(上层目录)和.
(当前目录),方法返回结果是拼接规范化后的完整路径。
import path from "path"
// 在POSIX系统中 拼接路径
const resPath = path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');
console.log(resPath); // "/foo/bar/baz/asdf"
③ path.resolve([…paths])
该方法用于将多个路径或路径片段从右向左拼接解析为一个绝对路径,并进行规范化处理,而且会根据当前平台自动使用对应的路径分隔符。从右向左处理的过程中,遇到第一个绝对路径就会停止处理,然后将当前路径作为起始路径,并将其右侧的路径拼接到该路径上,最终再规范;如果路径片段中没有绝对路径,则最终会以当前文件的工作目录作为起始路径。
路径片段必须都是String类型,其中可以包含..
(上层目录)和.
(当前目录),方法返回结果是拼接后的绝对路径,如果没有传入路径片段参数,则该方法将会返回当前文件工作目录的绝对路径。
import path from "path"
const resPath1 = path.resolve('/foo/bar', './baz');
console.log(resPath1); // "/foo/bar/baz"
const resPath2 = path.resolve('/foo/bar', '/baz', 'a', 'b');
console.log(resPath2); // "/baz/a/b"
const resPath3 = path.resolve('a', 'b', 'c');
console.log(resPath3); // "当前文件工作目录/a/b/c"
④ path.relative(fromPath, toPath)
该方法用于计算从 绝对路径fromPath 到 绝对路径toPath 的相对路径。如果两者指向同一个目录,则会返回一个空字符串;如果两者中存在空字符串,则表示使用当前文件工作目录。
import path from "path"
const resPath1 = path.relative('/home/user/docs', '/home/user/images/pic.jpg');
console.log(resPath1); "../images/pic.jpg"
⑤ path.parse(path)
该方法用于将路径字符串path 解析为一个路径信息对象,对象属性包含路径字符串的根目录(root)、目录路径部分(dir)、完整文件/目录名(base)、单独文件/目录名(name)和扩展名(ext)信息。
路径字符串可能最终指向一个文件,也有可能是指向一个目录。
import path from "path"
// 路径指向一个文件
const pathInfo = path.parse('/home/user/dir/file.txt');
console.log(pathInfo);
/* {
root: '/',
dir: '/home/user/dir',
base: 'file.txt',
ext: '.txt',
name: 'file'
} */
// 路径指向一个目录
const pathInfo2 = path.parse('/home/user/dir/files');
console.log(pathInfo2);
/*{
root: '/',
dir: '/home/user/dir',
base: 'files',
ext: '',
name: 'files'
}*/
除了该方法之外,path模块还提供了一系列相关方法来直接获取路径字符串中的部分信息:
- path.basename(path[, ext]) :获取路径字符串中的最后一级,如果是如果是个目录,则会返回目录名;如果是个文件,则会返回完成的文件名称,但可以通过第二个可选参数来去掉文件的后缀。
- path.dirname(path):获取路径字符串中的目录部分,即不包含最后一级的部分。
- path.extname(oath):获取路径字符串中的文件扩展名后缀,如果最后一级是文件,则正常返回扩展名后缀;如果最后一级是目录,则返回空字符串。
⑥ path.format(pathObj)
该方法与path.parse()
方法正好相反,用于将拥有根目录(root)、目录路径(dir)、完整文件名(base)、单独文件名(name)和扩展名(ext)属性的 JS 对象解析为一个路径字符串。
对象的这五个属性在解析为路径字符串时,存在一定的优先级关系:如果 dir 有值,则忽略root;如果 base 有值,则忽略 ext 和 name。
import path from "path"
const pathInfo = {
root: '/',
dir: '/home/user/dir',
base: 'file.txt',
ext: '.mp3',
name: 'file111'
};
const resPath = path.format(pathInfo)
console.log(resPath); // "/home/user/dir/file.txt"
⑦ path.isAbsolute() 等其他方法
暂不展开。
5、全局变量
除了 path 模块提供的方法外,CommonJS 环境中还提供了两个路径相关的全局变量,用于更方便的操作路径:
① __dirname
该全局变量表示当前文件所在目录的绝对路径。
② __filename
该全局变量表示当前文件的绝对路径,包含完成文件名,相当于: __dirname + /完整文件名。
const path = require("path")
// 假设文件位置:/home/user/node/test.js
console.log(__dirname); // "/home/user/node"
console.log(__filename); // "/home/user/node/test.js"
要注意的是这两个全局变量在 ESM 环境是无法使用的,不过可以借助 import.meta.url
和 url 模块实现类似的变量:
import path from "path"
import { fileURLToPath } from 'url';
// 假设文件位置:/home/user/node/test.js
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename)
console.log(__dirname); // "/home/user/node"
console.log(__filename); // "/home/user/node/test.js"