Node的buffer和fs文件模块

198 阅读8分钟

1.Buffer缓存区

  • 缓存区: 缓存区就是内存中开辟一块临时区域用于存储需要运算的字节码

  • 从结构上看Buffer非常像一个数组,他的元素为16进制的两位数

  • Buffer是一个和二进制很像的十六进制的字节码.两个段码表示一个字节,一个二进制表示一个位,一个字节是8个二进制位,一个16进制是2的4次方

2.Buffer模块

  • Buffer对象是node处理二进制数据的一个接口,它是Node原生提供的全局对象,可以直接使用,不需要require

  • JavaScript比较擅长处理字符串,对于处理二进制数据(比如TCP数据流),就不太擅长。Buffer对象就是为了解决这个问题而设计的。它是一个构造函数,生成的实例代表了V8引擎分配的一段内存,是一个类似数组的对象,成员都为0到255的整数值,即一个8位的字节

  • NodeJS是服务器端在处理像TCP(网络)流或文件流(也叫字节流),必须使用到 二进制数据.因此在NodeJs中增加了一个Buffer类,该类用来创建一个专门存放二进制数据的缓存区

  • 在node.js中默认使用utf-8编码,一个中文一般占3个字节

    //生成一个256字节的Buffer实例
    var bytes = new Buffer(256)
    //这个方法已经被废弃,需要改用Buffer.alloc() 
    var buf = Buffer.alloc(256)
    
  • Buffer作为构造函数,可以用New生成一个实例,可以接受多种形式的参数

    var str = 'Hello wrold'
    // 将一个字符串保存到buffer中
    var buf = Buffer.from(str)
    console.log(buf)
    // 注意from 的方法参数不能是数字
    // 类型可以为string, Buffer, ArrayBuffer, Array, or Array-like Object
    var buff = Buffer.from(buf)//拷贝,直接在内存中开辟空间
    
  • Buffer的存取操作[类数组]

    /*Buffer的存取操作--类数组*/
    var buf3 = Buffer.alloc(10)//开辟一个20字节的内存空间
    for(let i = 0;i<buf3.length;i++){
        buf3[i] = i;
        console.log("Buffer字节内容",buf3[i].toString());
    }
    
  • Buffer值的转换

    Buffer对象与字符串的互相转换,需要指定编码格式,默认utf-8格式

    /*Buffer与字符串的转换 
       buf.toString([encoding[,start[,end]]])
       encoding 可选,指定编码格式 默认是utf-8
       start 可选, 开始位置 默认最开始
       end 可选 结束位置 默认结束位置
      */
    let buf4 = Buffer.from('你好呀')
    // 不传参
    console.log(buf4.toString())
    // 指定编码, 以及转换的数据,一个中文占3个字节
    console.log(buf4.toString('utf8', 3,6))
    1.ascii
    2.utf8
    3.utf16le:UTF-16的小端编码,支持大于U+10000的四字节字符。
    4.ucs2:utf16le的别名。
    5.base64
    6.hex:将每个字节转为两个十六进制字符
    
    1. Buffer 是用于处理二进制数据流
    2. Buffer一个元素就是一个字节;
    3. 实例类似整数数组, 但和数组不一样的是,一旦实例,那么大小将固定
    4. 内存不 是由V8分配的, 是C++代码在V8堆外分配的物理内存

3.Buffer的基本方法

  • Buffer.isEncoding():返回一个布尔值,表示buffer实例是否为指定编码

  • Buffer.isBuffer():接受一个对象作为参数,返回一个布尔值,表示该对象是否为Buffer实例

  • Buffer.byteLength():返回字符串实际占据的字节长度,默认编码方式是utf-8

  • Buffer.concat() :将一组Buffer对象合并为一个Buffer对象

    如果Buffer.concat的参数数组只有一个成员,就直接返回该成员,如果有多个成员,就返回一个多个成员合并的新Buffer对象

    还可以接受第二个参数,指定合并和Buffer对象的总长度,省略该参数时,Node内部会计算出这个值,然后再据此进行合并运算,因此,显示提供这个参数,能提高运行速度

4.Buffer的实例方法

  • write()方法:可以向指定的Buffer队形中写入数据,第一个参数时所写入的内容,第二个参数(可省略)是所写入的起始位置(默认从0开始),第三个参数(可省略)是编码方式,默认为urf-8

  • slice()方法:返回一个按照指定位置,从原对象切割出来的Buffer实例,它的两个参数分别为切割的起始位置和终止位置

  • toString():方法将Buffer实例,按照指定编码(默认为utf8)转为字符串。toString方法可以只返回指定位置内存的内容,它的第二个参数表示起始位置,第三个参数表示终止位置,两者都是从0开始计算。

  • toJSON():toJSON方法将Buffer实例转为JSON对象。如果JSON.stringify方法调用Buffer实例,默认会先调用toJSON方法。

  • buf.fill():填充数据

      buf.fill(value [, start, [, end]])
      参数
      value 必须: 初始化数据
      start 初始化开始的位置
      end 初始化结束的位置
    
  • buf.copy():从目标buf中拷贝内容

    buffer.copy(目标buf, 写法buf开始的位置, 拷贝buffer开始位置, 拷贝buffer结束位置)
    let buf = Buffer.from('你好呀')
    let buf2 = Buffer.from([0,0,0,0,0])
    // 将buf里从下标3开始,到下标6的自己拷贝到 buf2 从buf2第一个字节开始写入
    buf.copy(buf2,1, 3,6)
    

5.Buffer的实例属性

  • length:length属性返回Buffer对象所占据的内存长度,该值与对象的内容无关
  • 不管写入什么内容,length属性总是返回Buffer对象的空间长度,如果想要知道一个字符串所占据的字节长度,可以将其传入Buffer.byteLength方法
  • length属性是可写的,但是这回导致未定义的行为,不建议使用,如果想要修改Buffer对象的长度,建议使用slice方法返回一个新的Buffer对象

6.fs模块

  • fs是filesystem的缩写,该模块提供本地文件的读写能力,基本上是POSIX文件操作命令的简单包装

  • fs模块的功能非常丰富. 并且,这个模块几乎对所有操作提供异步和同步两种操作方式,供开发者选择。

  • 文件读取方式

    ① 将硬盘是上的内容全部读入内容以后才触发回调函数,又称为直接读取

    ② 是流式读取:将数据从硬盘中读取一节就触发一次回调函数,也就是读取一节数据处理一节数据,实现大文件操作

    ③ 直接读取又分为同步读取和异步读取

  • readFile方法用于异步读取数据,使用一个回调函数来接受读取的内容

    readFile方法的第一个参数是文件的路径,可以是绝对路径,也可以是相对路径。注意,如果是相对路径,是相对于当前进程所在的路径(process.cwd()),而不是相对于当前脚本所在的路径。

    readFile方法的第二个参数是读取完成后的回调函数。该函数的第一个参数是发生错误时的错误对象,第二个参数是代表文件内容的Buffer实例。

    //直接读取文件--异步操作
      var fs = require('fs');
      fs.readFile("./src/demo.txt", function (err, data) {
          console.log(data)
          // err 错误对象,如果有错则有值,没有报错则为null
          // data 是Buffer数据流,如果需要显示数据通过data.toString
      })
      //第二个参数传入数据的格式,那么data将直接显示数据
      fs.readFile('./src/demo.txt','utf-8',function(error,data){
          console.log(data);
      })
    
  • readFileSync方法用于同步读取数据,采用赋值的方式来读取数据

    几乎所有的fs函数都有同步版本,只需要在异步版本的函数后面添加Sync即可 readFileSync方法的第一个参数是文件路径,第二个参数可以是一个表示配置的对象,也可以是一个表示文本文件编码的字符串。默认的配置对象是{ encoding: null, flag: 'r' },即文件编码默认为null,读取模式默认为r(只读)。如果第二个参数不指定编码(encoding),readFileSync方法返回一个Buffer实例,否则返回的是一个字符串。

    // 同步读取文件 同步采用赋值的方式返回数据 fs.readFileSync(文件路径)
    var data = fs.readFileSync('./src/demo.txt')
    console.log(data.toString());
    
  • 同步版本并没有报错接收,如果同步有错,系统将会自动报错,所以异步方法,如果有错系统不会报错,会将报错传给回调自行处理

  • writeFile方法用于异步写入文件。(默认直接覆盖)

    // 异步写入文件
    // fs.writeFile('文件名','数据',function(err){ /* 数据写入失败时的回调函数*/ })
    //可以传入写入的字符的编码,如果是一个Buffer,可以不指定
    var datas = "<h2>这是通过fs模块写入的文件</h2>"
    fs.writeFile('index.html',datas,function(error){
        console.log(error);
    })
    fs.writeFile('demo.txt','utf-8',datas,function(error){
        console.log(error);
    })
    

    fs.writeFile(file, data[, options], callback)

    1.file <string> | <Buffer> | <URL> | <integer> 文件名或文件描述符。

    2.data <string> | <Buffer> | <TypedArray> | <DataView> 写入的数据

    3.options <Object> | <string>写入数据的参数

    ①encoding <string> | <null> 数据编码格式:默认值: 'utf8'。

    ②mode <integer> 默认值: 0o666。

    ③flag <string> 参阅支持的文件系统标志。默认值: 'w'。

    4.callback <Function>回调函数

    ① err <Error>

  • 追加写入内容

    //fs.appendFile(文件路径, 数据, 回调函数)
    var data = "<h2>我是通过fs模块写入的文件</h2>"
    fs.appendFile("./test.html",data,(err) => {
        console.log(err)
    })
    
  • stat读取文件/文件夹信息 stats文件信息对象

    fs.stat("文件名",function(err,stats){ 
    	//stats是文件的信息对象,包含了常用个文件信息
    })
    //同步读取
    const stat = fs.statSync("./node")
    var fs = require("fs");
    fs.stat("index.json", function (err, stats) {
      console.log(stats)
    })
    /* stats对象
    Stats {
      dev: 226428394,
      mode: 33206,
      nlink: 1,
      uid: 0,
      gid: 0,
      rdev: 0,
      blksize: undefined,
      ino: 10414574138374020,
      size: 17,                                     //文件大小,字节数
      blocks: undefined,
      atimeMs: 1566988486138.9304,                 //
      mtimeMs: 1566625311439.442,
      ctimeMs: 1566625311439.442,
      birthtimeMs: 1566625188201.4536,
      atime: 2019-08-28T10:34:46.139Z,              // 上一次文件访问时间
      mtime: 2019-08-24T05:41:51.439Z,              // 文件内容修改时间
      ctime: 2019-08-24T05:41:51.439Z,              // 文件状态改变时间 
      birthtime: 2019-08-24T05:39:48.201Z           // 第一次创建时间
    }*/
    
  • stats对象方法

    stats.isFile() 判断是不是文件 stats.isDirectory() 判断是不是文件夹(目录)

    var fs = require("fs");
    fs.stat("index.json", function (err, state) {
     	console.log(stats.isFile())
    })	
    
  • rename修改文件名

      var fs = require("fs");
      fs.rename("./text", 'test.txt', err => {
          if(err) throw err
          console.log("done!")
      })
    
  • exists(path, callback)

    exists方法用来判断给定路径是否存在,然后不管结果如何,都会调用回调函数,回调函数的参数是一个表示文件是否存在的布尔值。

  • mkdir()/readdir()/fs.rmdir() 新增目录和读取目录中的文件列表及删除空文件夹

    mkdir方法用于新建目录。接受三个参数,第一个是目录名,第二个是权限值,第三个是回调函数。

    readdir方法用于读取目录,返回一个所包含的文件和子目录名字的数组。

    process.cwd是指当前进程所在的目录

    fs.mkdir("./src",function(err){
      // 创建失败的回调
      })
    fs.readdir("./src",function(err,list){
      console.log(err);
      console.log(list)
      // list 是读取文件夹列表
      })
      // 如果文件夹存在的话,list是 一个node文件夹下所有文件以及文件夹的数组集合
      // 如果文件夹不存在就是err
    
  • watchfile/unwatchfile()

    watchfile方法监听一个文件,如果该文件发生变化,就会自动触发回调函数。

    这里的文件变化, 分为通过代码修改文件和手动修改文件

    unwatchfile方法用于解除对文件的监听。

    // watch是监听 文件夹内所有的文件变化
    fs.watch("./",{
        recursive: false // 默认是flase 是否递归监听
    }, (changeType, filename) => {
        console.log(chaneType)  // 改变的类型 ,是修改一个文件 还是新增文件亦或是删除文件
        console.log(filename)  // 修改的文件名字
    })
    // watchFile 是监听一个文件的变化
    fs.watchFile('./node/1.txt',(c,p) => {
        console.log(p);
        console.log(c)
    })
    

    p监听文件改变之前的stats

    c监听文件改变之后(即当前文件)的stats

7.同步与异步

  • 同步操作会阻塞后面的代码, 后面的代码必须等待同步操作完成后方可执行;异步操作会先执行后面的代码, 等异步操作完成了就回头执行回调函数

  • 同步操作的结果直接作为一个值赋给变量

  • 异步操作必须有一个回调函数, 回调函数的第一个参数都是错误对象

  • 大多数node的异步api都有一个同步版本, api名称就是异步api+Sync

8.文件系统标识

当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+' 相似,但如果路径已存在则失败。

9.fs读写流方式

  • 用read读取文件方法,会将文件整体读入缓存区,然后使用write方法写入文件的时候,会将文件整体读入缓存区,再修改后也会整体写入文件。

  • 在node中无论read还是write都是把文件视为一个整体.Node需要在内存中开辟与文件相同大小的缓存空间,会出现文件过大,超过内存大小的情况。

  • stream流:应用程序中,流是一种有序的,有起点和终点的字节数据的传输方式.在应用程序中各种对象之间交流与传输数据的的时候,总是先将该对象中所包含的数据转换为各种形式的流数据(即字节数据),再通过流的传输,到达目的对象后再将流数据转换为该对象中可以使用的数据

  • 所有互联网传输数据都是以流的方式传输的,也就是说都是一节一节的

10.流的操作

  • 以流的方式读文件

    // 1. 创建读取流
    var stream = fs.createReadStream(path)
    // 2. 绑定data事件接受数据
    stream.on("data",function(data){
        console.log(data)
        console.log(1) // 打印几次,表示数据分了几次流
    })
    // 3. 绑定end事件表示读取完毕
    stream.on("end",function(){
        console.log("数据流读取完毕")
    })
    // 4. 绑定error错误事件
    stream.on("error",function(err){    
        throw err   
    })
    
    var fs = require("fs");
    // 创建一个可读流
    var stream = fs.createReadStream("./file2.txt");
    // 监听data事件,每读一段流数据就执行一次回调函数
    stream.on("data", function (data) {
      console.log(data)
    })
    

    每一节数据流的长度是65536字节,大小是65536/1024=64kb,也就是每一节的大小是64kb,可以通过data.length查看

    读取流事件:end 表示在读取流读取完毕后触发的事件,error 表示在读取流读取错误时后触发的时间

    stream.on("end", function () {
      console.log("读取完毕")
    })
    
  • 以流的方式写文件

      // 1. 创建写入流
      var stream = fs.createWriteStream(path)
      // 2. 写入数据
      stream.write("数据1")
      // 3. 写入完毕后结束写入流
      stream.end()
      // 4. 数据写入完成事件
      stream.on("finish",function(){})
      // 5. 绑定error出错事件
      stream.on("error",function(err){})
    
    var fs = require("fs");
    //  创建一个写入流
    var stream = fs.createWriteStream("./file3.txt");
    // 写入数据
    stream.write("面包吃完了");
    stream.write("牛奶喝光了");
    stream.write("钱也花完了");
    stream.write("工作也丢了");
    stream.write("最悲催的是我迷路了");
    stream.end();     // 已流的方式写入数据必须显示的声明结束
    //写入流可以通过监听finish事件,来处理在写入流完成后要做的事情
    stream.on("finish", function () {
      console.log("写入完成")
    })
    //写入流错误事件,error,在写入错误时触发回调
    stream.on("error", function (err) {
      console.log(err)
    })
    

原文链接