node的接口中最有意思的就是文件接口,文件接口中有两个核心模块:buffer、fs模块。
buffer模块(了解即可,只是为fs模块做铺垫的)
众所周知,无论编程语言如何去写,一个文件无论是文本、是图片、是视频还是音乐等等,换算到最底层,其实就是二进制数据0和1的各种组合。
Buffer对象是Node处理二进制数据的一个接口。它是Node原生提供的全局对象,可以直接使用,不需要require('buffer')。如下:
JavaScript比较擅长处理字符串,对于处理二进制数据(比如TCP数据流),就不太擅长。Buffer对象就是为了解决这个问题而设计的。它是一个构造函数,生成的实例代表了V8引擎分配的一段内存,是一个类似数组的对象,成员都为0到255的整数值,即一个8位的字节。
Buffer是一个和二进制很像的十六进制的字节码,两个段码表示一个字节
一个二进制表示一个位,一个字节是8个二进制位,一个16进制是2的4次方
buffer 创建
Buffer是一个全局的类,不需要加载可以直接使用。
创建指定长度的缓存区
在node.js中默认使用utf-8编码,一个中文一般占3个字节
// 创建10个字节的缓存区
var buf = new Buffer(10)
// Buffer()的写法已经弃用, 需要改用Buffer.alloc()
// 写入一个字符a
buf.write('a')
console.log(buf)
以上这个方法已经被废弃,不要在使用了。
指定数组长度创建Buffer
var buf = Buffer.alloc(10)
console.log(buf)
以上这个方法也没什么意义,毕竟我们不知道要存的内容是多大。
使用 from 创建Buffer
var str = 'Hello wrold'
// 将一个字符串保存到buffer中
var buf = Buffer.from(str)
console.log(buf)
// 注意from 的方法参数不能是数字
// 类型可以为string, Buffer, ArrayBuffer, Array, or Array-like Object
buffer的基本方法
Buffer.isEncoding():Buffer.isEncoding方法返回一个布尔值,表示Buffer实例是否为指定编码。如下:
Buffer.isBuffer():Buffer.isBuffer方法接受一个对象作为参数,返回一个布尔值,表示该对象是否为Buffer实例。如下:
Buffer.byteLength():Buffer.byteLength方法返回字符串实际占据的字节长度,默认编码方式为utf8。如下:
Buffer.concat():Buffer.concat方法将一组Buffer对象合并为一个Buffer对象。如下:
需要注意的是,如果Buffer.concat的参数数组只有一个成员,就直接返回该成员。如果有多个成员,就返回一个多个成员合并的新Buffer对象。Buffer.concat方法还可以接受第二个参数,指定合并后Buffer对象的总长度。省略第二个参数时,Node内部会计算出这个值,然后再据此进行合并运算。因此,显式提供这个参数,能提供运行速度。
buffer的实例方法
write():write方法可以向指定的Buffer对象写入数据。它的第一个参数是所写入的内容,第二个参数(可省略)是所写入的起始位置(默认从0开始),第三个参数(可省略)是编码方式,默认为utf8。如下:
slice():slice方法返回一个按照指定位置、从原对象切割出来的Buffer实例。它的两个参数分别为切割的起始位置和终止位置。如下:
toString():toString方法将Buffer实例,按照指定编码(默认为utf8)转为字符串。如下:
注意,toString方法可以只返回指定位置内存的内容,它的第二个参数表示起始位置,第三个参数表示终止位置,两者都是从0开始计算。如:
toJSON():toJSON方法将Buffer实例转为JSON对象。如果JSON.stringify方法调用Buffer实例,默认会先调用toJSON方法。如下:
buffer的实例属性
length:length属性返回Buffer对象所占据的内存长度。注意,这个值与Buffer对象的内容无关。
上面代码中,不管写入什么内容,length属性总是返回Buffer对象的空间长度。如果想知道一个字符串所占据的字节长度,可以将其传入Buffer.byteLength方法。
length属性是可写的,但是这会导致未定义的行为,不建议使用。如果想修改Buffer对象的长度,建议使用slice方法返回一个新的Buffer对象。
fs模块(node中最重要的模块,没有之一)
fs是filesystem的缩写,该模块提供本地文件的读写能力,基本上是POSIX文件操作命令的简单包装。如下图所示:
fs模块的功能非常丰富,并且,这个模块几乎对所有操作提供异步和同步两种操作方式,方法名没有加Sync的是异步方法,加了Sync的是同步方法。一般在没有特殊需求的情况下,强烈不建议用同步方法,因为不能确定当前自己的服务器硬盘有没有卡死。
fs模块之readFile()方法
fs.readFile(path, callback)
readFile方法用于异步读取数据,读取出来的结果就是一个buffer(一个二进制数据)。可接收两个参数。
readFile方法的第一个参数是文件的路径,可以是绝对路径,也可以是相对路径。注意,如果是相对路径,是相对于当前进程所在的路径(process.cwd()),而不是相对于当前脚本所在的路径。
readFile方法的第二个参数是读取完成后的回调函数。node的回调函数是错误先行,该函数的第一个参数是发生错误时的错误对象,第二个参数是代表文件内容的Buffer实例。
如下所示:
fs模块之writeFile()方法
writeFile方法用于异步写入文件。(默认直接覆盖)
该方法可接收四个参数:fs.writeFile(file, data, [options], callback)
第一个参数表示你要把数据写在哪一个文件里面
第二个参数表示你要写入的数据
第三个参数表示文件的配置信息:而配置信息中也可以设置如下信息:
encoding <string> | <null> 默认值: 'utf8'。
mode <integer> 默认值: 0o666。
flag <string> 参阅支持的文件系统标志。默认值: 'w'。
第四个参数就是回调函数
第三个参数flag的补充
因为在同一个文件中写入新的信息,默认是直接覆盖,所以当第三个参数的flag选项采用字符串时,可用以下标志:
'a':打开文件用于追加。如果文件不存在,则创建该文件。
'ax':与 'a' 相似,但如果路径已存在则失败。
'a+':打开文件用于读取和追加。如果文件不存在,则创建该文件。
'ax+':与 'a+' 相似,但如果路径已存在则失败。
'as':以同步模式打开文件用于追加。如果文件不存在,则创建该文件。
'as+':以同步模式打开文件用于读取和追加。如果文件不存在,则创建该文件。
'r':打开文件用于读取。如果文件不存在,则出现异常。
'r+':打开文件用于读取和写入。如果文件不存在,则出现异常。
'rs+':以同步模式打开文件用于读取和写入。指示操作系统绕过本地的文件系统缓存。
这对于在 NFS 挂载上打开文件时非常有用,因为它允许跳过可能过时的本地缓存。 它对 I/O 性能有非常实际的影响,因此除非需要,否则不建议使用此标志。
这不会将 fs.open() 或 fsPromises.open() 转换为同步的阻塞调用。 如果需要同步的操作,则应使用 fs.openSync() 之类的。
'w':打开文件用于写入。如果文件不存在则创建文件,如果文件已存在则覆盖文件。
'wx':与 'w' 相似,但如果路径已存在则失败。
'w+':打开文件用于读取和写入。如果文件不存在则创建文件,如果文件已存在则截断文件。
'wx+':与 'w+' 相似,但如果路径已存在则失败。
写入文件
尝试一下写入一个txt文件,如下图所示:
上文说过如果在同一个文件中写入新的信息,默认会直接覆盖掉原来的信息,但如果我们不想直接覆盖原来的内容,而只是想在原来的基础上追加新的信息,比如我们想在helloworld.txt文件中的“你好世界”后面继续追加写入“你吃了吗”,可以这样操作:
这样便完成了对已有文件信息的追加写入,既然可以写入一个txt文件,能不能写一个图片进来呢?比如说写入下面这张美少女的图片:
首先我们需要获取到想要写入的图片数据源,(因为不可能手动输入一个图片的二进制数据,15万字节,所以需要先读取到需要写入的图片)使用fs的同步读取方法readFileSync(为啥用同步,仅仅只是示范一下同步的使用方法),读取到该图片的二进制数据,并将其保存到imgData中,然后再使用fs的写入方法writeFile,将该图片写入到名为"美少女.jpg"的文件中,如此便成功写入一张图片啦,如下图所示:
fs模块之exists(path, callback)
fs.exists(path, callback)
exists方法用来判断给定路径是否存在,然后不管结果如何,都会调用回调函数。回调函数的参数是一个表示文件是否存在的布尔值。exists方法可以用于写爬虫的时候,如果该爬取到的文件已经被写入过了,就不用重复写入了。
比如我们想要将“你吃饱了吗”写入"helloworld.txt"文件中,但是如果"helloworld.txt"文件已经存在的话,那我们就不写入了,如果该文件不存在,我们就写入,就可以使用exists方法来判断文件是否存在。因为"helloworld.txt"文件在上文已经创建并写入过了,所以并不会执行新的写入操作,如下图所示:
fs模块之mkdir()/readdir()
fs.mkdir(pathname, limit, callback)
mkdir方法用于新建目录。
mkdir接受三个参数,第一个是目录名,第二个是权限值,第三个是回调函数。
比如我们想在当前路径下创建一个新的目录文件夹"setting",可以如下操作:
fs.readdir(path, callback)
readdir方法用于读取目录,返回一个所包含的文件和子目录名字的数组。
接收两个参数,想要读取的路径和回调函数。如果想读取当前进程所在目录,可以传入process.cwd(),指的是当前进程所在的目录。如下所示:
fs模块之stat
当我们获得了目录或是文件信息后,要进行使用,那么就得判断一下这个信息是目录还是文件名,不然直接调用readfile方法读取文件必然会出现访问失败的情况。
此时就可以使用fs.stat(filename,callback)来判断其是否是文件或者文件夹
stat方法的第一个参数是一个文件或目录,它产生一个对象,该对象包含了该文件或目录的具体信息。我们往往通过该方法,判断正在处理的到底是一个文件,还是一个目录。
比如我们读取到了当前进程下的目录,我们想要知道其中哪些是文件夹,哪些是文件,并且想找出文件中的js文件(我们能找出js文件,同理也能找出其他类型文件进行操作),可以如下操作:
fs模块之watchfile/unwatchfile()
fs.watchfile(filename,callback)
接收两个参数,文件名和回调函数。
watchfile方法监听一个文件,类似工程化中的热更新技术(当发现某些文件变动了,重新执行某些程序),如果该文件发生变化,就会自动触发回调函数。
unwatchfile方法用于解除对文件的监听。
比如我监听"helloworld.txt"文件,并且修改其中原来的内容,在后面加入一句“吃了呀”,如下所示:
如此便完成了对文件内容的监听。
(未完待续)