FS文件系统

898 阅读7分钟

FS文件系统

Fs模块全称:file system(文件系统)。

Fs模块其实就是node中自带的核心模块,用来管理和操作服务器上的文件的。

fs模块的底层依赖和工作方式

我们之前已经了解过“流”和“事件”的模块了,而在node源码中fs模块就是依靠“流”和“事件”来完成的,当然中间还牵扯到一个叫做“缓冲区”的东西,但是缓冲区里面的数据都是用二进制的十六进制表示,打印出来我们也看不懂,暂时不做太多了解。

fs的工作方式(以写文件为例)

在这里我们需要了解下在程序中我们么的fs模块是如何实现往一个文件中写入数据的步骤的。
首先我们需要两个对象:

  • 程序对象(运行依赖cpu)
  • 磁盘对象(被动等待程序调用)

一个正常的往文件中写入数据的步骤应该是:

  • 程序开始执行
  • 程序检测到需要往文件中写数据
  • 开始读取磁盘
  • 往磁盘中写数据
  • 数据写入完成

上面的步骤看起来没有任何逻辑问题,但是这里有一个小问题需要我么注意,磁盘的操作和运行速度和cpu相比是超级慢的,这也就是意味着程序往磁盘中写数据的时候往往是程序经常是需要等待磁盘工作完成才能继续往下执行,这样的话如果出现了数据量很大的情况就意味着程序会被动的被磁盘操作拖慢速度,这么一看是不是就很不合理了?

所以一般情况下我们程序对磁盘的操作都是异步的,所以fs模块在执行往文件中写入数据的步骤应该是下面这样的:

  • 程序开始执行
  • 程序检测到需要往文件中写数据
  • 开始读取磁盘
  • 往缓冲区中写数据
  • 程序继续执行
  • 缓冲区往磁盘中写数据
  • 数据写入完成

上面的fs模块操作磁盘的操作其实就是我们node.js一开始对磁盘操作的逻辑,但是并不是任何情况下异步操作都是非常合适,所以也就有了同步操作数据的方式(也就是第一种步骤)。在绝大部分情况下其实我们都是希望数据是异步写入磁盘中的,这时候ES6标准终于出现了,所以在node.js v10的版本中出现了Promise对象操作磁盘的选择,下面我们就一起来看一下这些知识点是怎么回事吧。

关于fs模块我们学习以下知识点

  • 常用API(异步)
  • 常用API(同步)
  • fs.Promise版

常用API(异步)

学习fs模块只需要把握住两个重点即可:

  • 程序是如何读数据的
  • 程序是如何写数据的

没错,fs模块就只有两个重点,读和写。

下面就是我们需要掌握的异步API:

  • fs.stat():检测第一个参数是文件还是目录
  • Fs.mkdir():创建目录
  • fs.writeFile():创建文件
  • Fs.appendFile():追加文件
  • Fs.readFile():读取文件
  • Fs.readdir():读取目录
  • Fs.rename():修改名称
  • Fs.rmdir():删除目录
  • Fs.unlink():删除文件
  • 管道流

fs.stat()

该函数用于检测第一个参数是文件还是目录。
该方法中拥有两个参数:

  • 第一个是指定检测的文件或者目录名
  • 第二个参数是一个回调函数,拥有两个参数,error(错误参数),stats(成功参数,是一个对象)

Stats对象中拥有两个方法

  • isFile():返回布尔值,是文件就返回true
  • isDirectory():返回布尔值,是文件夹就返回true

案例如下:

    // 因为fs模块不是全局的,所以需要引入
    const fs = require("fs")
    // 读取项目根目录下的a.txt文件
    fs.stat("./a.txt",(err,stats)=>{
        // 如果出现文件读取错误
        if( err ){
            console.log(err)
            return
        }
        console.log("文件:"+stats.isFile());// true
        console.log("文件:"+stats.isDirectory());// false
    });

Fs.mkdir() 该方法用以创建目录,接收两个参数:

  • 参数1:将来被创建的目录名
  • 参数2:回调函数

案例演示:

    // err:错误参数
    fs.mkdir("./pages",err=>{
        if(err){
            console.log(err)
            return
        }else{
            console.log("文件夹常见成功");
        }
    });

fs.writeFile() 该方法会往一个文件中写入数据,如果该文件不存在则会先创建文件再写入数据。
该方法的参数有四个:

  • 参数1:filename(String) 文件名称
  • 参数2:data(String|Buffer) 将要存进文件中的内容
  • 参数3:options(配置选项) 一般不写,使用默认
  • 参数4:callback(回调函数) 传入一个异常参数err

案例如下:

    // 在项目的根目录下并没有b.txt这个文件
    // 程序会先创建b.txt文件
    // 再往该文件中写入数据
    fs.writeFile("./b.txt","这是fs模块写入的数据",err=>{
        if(err){
            console.log(err)
            return
        }else{
            console.log("数据写入成功")
        }
    });

appendFile()

该方法与writeFile()方法类似,但是在文件已经存在的情况下,它所执行的操作不是覆盖原文件内容,而是追加。参数配置等都和writeFile()方法相同。

readFile()

该方法为读取文件内容,参数有三个:

  • 参数1:filename
  • 参数2:options(一般使用默认)
  • 参数3:callback

案例演示:

    // err:错误参数
    // data:数据参数
    fs.readFile("./b.txt",(err,data)=>{
        if(err){
            console.log(err)
            return
        }else{
            // 读取到数据会以Buffer字符串的形式返回(二进制的数据以十六进制的形式表示)
            console.log(data)
            // 将Buffer字符串转换成普通字符串
            console.log(data.toString())
        }
    });

readdir() 这个方法功能为读取目录,使用方法和readFile()方法很类似,参数也是一致的。

案例演示:

    // 参数1:目录名
    fs.readdir('./dir_name',(err,data)=>{
        if( err ){
            console.log(err);
            return
        }else{
            // data为存有指定目录下所有的文件名和目录名的数组
            console.log(data);
        }
    });

fs.rename 该方法用以修改文件或文件夹的名称,有三个参数:

  • 参数1:oldpath 将要被修改的文件/文件夹的路径和名称
  • 参数2:newpath 指定修改的名称
  • 参数3:callback 回到函数(参数error)

案例演示:

    fs.rename('./b.txt',"./c.txt",(err)=>{
        if(err){
            console.log(err)
        }else{
            console.log("重命名成功")
        }
    });

fs.rmdir() 该方法用以删除指定的目录,但是使用的时候需要注意,如果指定删除的目录为非空目录则会删除失败。

案例演示:

    fs.rmdir("./pages",(err)=>{
        if(err){
            console.log(err);
        }else{
            console.log("删除成功")
        }
    });

fs.unlink

该方法用以删除指定的文件。

案例演示:

    fs.unlink('./a.txt',err=>{
        if(err){
            console.log(err);
        }else{
            console.log("删除成功")
        }
    });

管道流 管道流大致上可以理解成:数据源和目标源直接架设一根管道,让数据像水流一样的传输过去,注意的会大大提成数据传输的速度。

案例如下:

    // 创建文件可读流
    const readStream = fs.createReadStream("./1.mp4")
    // 创建文件可写流
    const writeStream = fs.createWriteStream("./2.mp4")
    // 管道流传输数据
    readStream.pipe(writeStream);

以上就是我们常用的fs模块的异步api了,但是这里特别说明,我们这里介绍的并不是全部,例如可能我们有时候会判断某个目录中是否包含指定的目录,这时候就需要用到 fs.existsSync() 方法,fs莫块状的方法是在太多,这里我们就不做一一介绍了,如果希望深入学习还是去读一读官网文档比较好。

常用API(同步)

如果你已经理解的异步和桶的含义的话,那么对于fs模块中的同步API学习就非常简单了。
总结下同步API的特点,首先不需要回调函数,再来就是方法执行完之后拥有自己特定的返回值,而在语法上方法名也非常好记。

  • fs.statSync():检测第一个参数是文件还是目录
  • Fs.mkdirSync():创建目录
  • fs.writeFileSync():创建文件
  • Fs.appendFileSync():追加文件
  • Fs.readFileSync():读取文件
  • Fs.readdirSync():读取目录
  • Fs.renameSync():修改名称
  • Fs.rmdirSync():删除目录
  • Fs.unlinkSync():删除文件

举例:

    let data = fs.readFileSync("./c.txt").toString()
    console.log(data);// 这是fs模块读取到的数据

fs.Promise

fs.promises API 提供了一组备用的异步文件系统的方法,它们返回 Promise 对象而不是使用回调。 API 可通过 require('fs').promises 访问。
没错,在很多的情况下我们其实都是需要使用异步的处理磁盘数据的,传统的写法是使用回调函数,而业务复杂了之后就容易出现**回调地狱(callback hell)**的情况,ES6中的Promise就是为了解决这种情况而生的。

注意:想要使用fsPromise你电脑上 node.js 的版本必须是 v- 10.0.0 及以上

关于fs.Promise的API很多,这里还是只给大家举例说明。

案例如下:

    fsPromise.readFile('./c.txt')
    .then(data=>{
        console.log(data.toString())
    })
    .catch(err=>{
        console.log(err)
    });