就算步子乱了又如何,接着跳下去就好了。——《闻香识女人》
hello,大家好,我们来学习一下buffer。首先来看看 buffer 是一个什么东东。buffer,中文译为缓冲区,是一个类似于数组的对象,用于表示固定长度的字节序列。这个字节序列大家可能并不是特别懂,所以我们换一句话来说这个buffer,它就是一段固定长度的内存空间,主要用于处理二进制的数据。
那么下边我们看一看特点。
首先的话 buffer 它大小固定,并且大小不能调整,这一点跟数组是不太一样的,我们知道数组是可以新增,可以删除元素的。其二, buffer 性能较好,因为它可以直接操作计算机内存。第三个,每个元素就是 buffer 当中的每个元素大小为一个字节。这里强哥列了个图,这里边每一个 0 或者一表示是一个bit(比特),而 8 个 bit 所组成的空间把它称之为是一个字节。下边的话我们在代码层面来给大家演示一下 buffer 的相关操作。
首先我们先建一个文件,我们先来看一看关于 buffer 的创建。 buffer 的创建主要有三种方式,第一种方式是 alloc 方法,第二个是 allocUnsafe,第三个是通过 from 这三种方式来创建。我们首先来看看第一种创建方式,声明一个变量buff,然后 Buffer 点 alloc,括弧里边儿我们传一个数字,我给个10。这行代码儿的含义是创建一个十字节的buffer,其中这个 buffer 是 nodeJS 的内置模块儿,在启动时已经将这个模块儿自动的加载进来,所以我们不需要手动导入就可以使用。
这里大家可以认为这个 Buffer 是一个全局变量,然后 alloc 这个单词的含义是分配的意思,
在这它是 Buffer 对象里边一个方法,那么下边我们通过 log 来打印一下 buff 这个变量,右键终端打开,然后用 node 点杠 去执行一下这个 js 文件,敲回车,大家可以看到这是 buffer 的输出结果。
说明一点,用 alloc 这种方法创建的Buffer,每一个二进制位都会归零。
OK,那么下边我们来看看第二种 buffer 的创建方式,给个变量等于 buffer 点 allocUnsafe。在这里 Unsafe 单词的本意是不安全的意思,我们还是一样来打印一下buf_2。 2 这里我们可以通过方向键上键来调出刚才那个命令的历史,调出来之后直接敲回车就可以运行。
大家会发现这个输出结果跟刚才 a lock 那个输出结果是一模一样的。为什么这里要标识一个不安全?这里要稍微解释一下。因为用 allocUnsafe 这个方法,它所创建的buffer,其中可能会包含旧的内存数据。同学可能会想,为什么会包含旧的内存数据?这里我稍微解释一下。内存空间它其实是可以复用的,比如说 a 程序要执行它申请了一段内存空间,等它执行完毕之后,这段空间就没有人在用了,所以当 b 程序在执行时,就可以再次去使用这段内存空间。而用 a lock 这种方式在创建时,它会对里边每一个二进制位都清零。但用 allocUnsafe 这种方式在创建 buffer 时,它并不会对旧的数据做一个清空,做一个归零。所以说用 Unsafe方法所创建的buffer,它其中是会有旧数据的。
我来给大家演示一下,比如说我在这里把这个数值加大,
然后按上,敲回车,大家可以看一下,这些就是之前程序遗留的内存数据,
并且每一次查看它这个结果都不太一样。
所以说对于这样的buffer,你在用的时候一定要格外小心,稍有不注意就会导致程序出现一些问题。
那这里同学可能会有另外一个困惑说,那既然这块不安全,为什么不直接使用 alloc?再来说明一下子就说用这种方式去创建buffer,它的速度要比 alloc 要快一些,因为它毕竟不需要做归零这样一个操作,只需要在用的时候稍微注意一下就可以了。
好,那么下边我们再来说一下第三种创建方式。这里我们可以通过 Buffer.from,可以将一个字符串或一个数组转为buffer。这个 from 单词的本意是来自于的意思,比方说我这里写一个hello,好,咱们接下来打印一下子这个 buff下划线三,然后按上,敲回车。然这里多输出了一个,我们把这个 2 先注释掉,然后按上敲回车。
大家可以看一下这个结果, 68 65 6C,我们再来执行一次这个结果还是 68 65 6C,那么这个 68 65 6C 它是怎么来的?要稍微解释一下。那么在转换时,这个每一个字母都会转换为这个字符在 Unicode 码表当中对应的那个数字,然后这个数字会转成二进制,然后存到 buffer 当中,
我们来验证一下子,打开浏览器,以 'h' 来为例,就是第一个字符这个 h 在 asking 码表当中,这个数对应的数字是104。
同学可能会说,强哥刚才不是说 Unicode 码表吗?怎么又来开始整这个 ASCII 码表?稍微解释一下,因为这个 Unicode 码表是完全兼容 ASCII 码表的,所以它对应的数字其实是一样的, h 对应的是104。
好,回到vscode过来,咱们再看一下,同学说不对是吧?这不是 68 嘛,怎么能是104?
再稍微解释一下,这个 68 它其实是一个 16 进制的表示方式,
我们切过来清空一下子,我把 68 放进来看一下,十六进制68,它所对应的十进制就是104,所以这块儿是没有问题的。
对应的后面的 'ello' 也都是一样,它们会转成数字,然后存到 buffer 当中。
那么其次这个 from 它还可以把数组也转成buffer。举个例子,比如说4,然后等于一个 Buffer.from ,数组,我提前准备好了,大家来看一下。
看这个,把咱们复制一份,然后拿过来放在这,然后我们输出一下 buf_4 在转换的过程当中,数组里面的每个数字都会转成二进制,然后存到 buffer 当中,我们来验证一下效果,按上然后敲回车。
大家可以看一下这个 buffer 就已经是出来了,这里再来做一个补充,就是 buffer 这块内容对于大家第一次接触人来说还是有一定的困难的,不过大家不必太过于担心,把代码敲一敲,练一练,对他有一个认识就可以了。
我们来学习一下 buffer 的操作与注意事项。首先的话我们先来看看第一个操作,就是 buffer 与字符串的转换,这里我们又建了一个文件,首先我们来看看 buffer 与字符串的转换,首先我们先把上一个小节所创建那个 buffer 拿过来,就是这个 buffer_4拿过来之后,
我们可以把这个 buffer 转换成字符串。怎么来转换?可以借助于 buffer_4 这个 对象里边的一个方法,叫做toString。好,那么接下来我们来看效果,右键终端打开,
然后 node 2- table(tab键补全) 敲回车,大家可以看一下,叫做 i love you,这个就是 buffer 转成字符串之后的结果,也可以算作是一种花式表白的方式。
当然这里要顺便说一下,在转换字符串时,它是默认采用的 UT F-8 的编码方式做的这个字符串转换,当然还有一些别的编码方式的转换,因为用的不多,所以我们这里就不再一一介绍了。
好,下面的话我们来看一下第二个操作,就是关于 buffer 元素的读写。我们说过的这个buffer,它是类似于数组的一个元素,所以我们可以通过中括号下边的方式来对 buffer 里边的元素进行操作。举一个例子,比方说我们这个 buffer 先创建一下子,以这个 hello 来为例,我们获得一个buffer,现在我想获取一下第一个,也就是这个('h')第一个元素,它里边所保存的数据该怎么来获取呢?我们可以这样来做,就是通过 buff 下标 0 来获取。好,我们来看看效果。
摁上敲回车,大家可以看一下这个值是104,这是一个十进制的表示方式,
如果说你想看看它底层二进制的表示形式,我们可以在这个数字的后边加一个 toString,来一个2。
注意这个(下面的) toString 跟这个(上面的) toString 可不是一个意思,咱这个(下面的) toString 是进行数字的进制转换的。好,摁上敲回车可以来看一看,它这个值是1101000。
同学说不应该是8位吗?怎么是 7 位?其实在这个第一位的前边,它还有一个0,所以最后它真正所保存的值应该是01101000,这是对于 buffer 里边单个元素的一个查看。
那比如说我还想对里边的单个元素做一个修改,可不可以呢?这个也是可以的,比方说我们写一个 buff 来一个0,然后等于一个95,举个例子。
好,那么 95 改完之后,我们来看看打印一下 buff 改完之后,这个值到底有没有变化?按上它回车看一下这个值是5F,之前是多少,咱们可以来看看在改之前那个值可以做一个输出,然后按上敲回车,之前是68,现在变成了5F,所以这个值它是可以更改的。
并且我们还可以看看这个 buffer 在改完之后转成字符串,它长什么样子,按上它会吃它变成了一个下划线,然后 ello 了。
好,这是当前的话关于 buffer 的第二个操作,就是通过中括号的方式对其中元素做一个读取和写入。
下边我们来对这个 buffer 做一个补充说明,主要是有两点,第一个是关于溢出,第二个是关于中文。那怎么一个溢出法?我们来举个例子,就以刚才这个 hello 来为例,把代码打开,我们这里写一个buff,下标来一个 0 等于 1 个361。同学可能会想说,这 361 有什么问题吗?当然有问题,啥问题?就是 8 个二进制位,它所能存储的最大数字是255,这里我们可以打开计算器给大家来看一下,选择二进制,输入八个1。
大家注意观察,十进制最大数字是255,你这里给一个 361 用八个二进制位肯定是装不下的,那么在这种情况下,你再尝试去修改单个字节的值,那么这一步它会做一个事情,就是舍弃高位的数字。怎么一个舍弃法呢?就是把高于8位的那些数据全部都丢掉,我们来演示一下,给大家,给大家演示一下它的一个过程。
361,咱们先来看一看361,它转成二进制位是这样的一个值,
这是四位,这是四位,这是一位,
其实总共是九个二进制来表示361。然后在保存时, node 会把高于8位这些数据全部都舍弃掉,所以真正在写入时,它其实长这个样子,拿过来。
然后咱们再来看看这个内容转成十进制是多少,然后在这里选择二进制 control v,
它是105,十六进制是69,注意十六进制是69,我们来看看改完之后它有没有变化。 console log 打印一下 buff 来运行一下,摁上怎么回事?可以看一下,此时它变成了69,确确实实是把高于8位内容给舍弃的。
当然这里我要再补充一点,这块内容大家了解即可,咱们后边儿是用不到的。
那么其次还有一个注意事项是关于中文的,我把这代码再 copy 一份儿,然后我们这里不写英文字符了,来一个。你好,两个汉字。我们来看看这个时候 buff 它的一个结果,按上敲回车,大家可以看一下这个时候的结果,它其实是 6 个字节,并不是两个字节。
为什么不是两个字节?因为这两个中文它是 UTF-8 的中文,大家注意观察这个位置是 UTF-8 的中文,
而一个 UTF-8 的中文字符,它一般是占三个字节的,在这里'你'占3个。‘好’占3个,那么这小节关于 buffer 的操作与注意事项,我们就先说这么多。
这个环节我们来介绍一下计算机的基本组成,首先的话我们来先看看三大零部件,第一个是CPU,又被称之为是中央处理器,是整个计算机运算和控制的中心,就是我们的程序,哪怕是最简单的 1 + 1 这样的运算,最后也都是由 CPU 来完成的。
那么其二是内存,是存储数据的一个介质,可以在里边存放大量的 0 和1这样的数据。那其二是硬盘,它跟内存很像,也可以存储很多的01这样的数据。
那同学可能会有一个问题,那既然内存和硬盘都可以存储数据,那为什么要做两个设备?这里我们首先给大家做一个补充说明,内存它存储数据有一个特点,读写速度特别快,但是在断电之后它会丢失数据,那我们的程序在运行时都会载到内存当中,然后像 CPU 高速的对这个数据做一个执行和处理,那同学可能会说那这个东西断电之后就丢了,这个你别着急,这个时候硬盘的作用就体现出来了,硬盘它读写速度相对较慢,但是它断电之后不丢失数据。像咱们平时下载一些程序,英雄联盟、浏览器、 QQ 这些程序下载安装完之后都是装到了硬盘里边,而且装完之后断电之后它是不消失的。
下边的话我们再认识另外一个元器件,这个元器件的名字叫做主板,主板是一块大的集成电路板,然后上面有很多插槽,
我们刚才所说这些个器件就是插在插槽里面,通过主板连在一起的,
然后还有另外一个器件我们平常也会听到,就是显卡是负责处理视频信号的,当有信息需要呈现时,需要在显示器当中呈现时,就会将信号传递给显卡。显卡处理完毕之后,再把信号传递给显示器,然后显示器最终显示,然后显卡是插在这个位置的,当然了这个位置也可以插好了。
把这块组装完毕之后,我们再把这些元器件放到一个机箱当中,这个电脑就组装差不多了,可以看一下这是组装之后的一个效果,
然后眼前这个放光的它其实是散热器,
在散热器的下边是CPU,为什么装散热器?嗯,简单说一下,是因为这个 CPU 它工作时会产生大量的热量,热量的话需要散走传递出去,否则的话会影响它运算的一个效率,而这个就是散热用的,那这个散热器的话有的是水冷的,有的是风冷的,都行,目的都一样,反正就是为了把热量散出去。
然后这个是内存条,这四个发光的,他们是内存条。
然后眼前这个 RTX 这个东西是显卡,然后显卡的话它后边大家可以看一下,有好几个这个输出的视频口,到时候会接上显示器,然后把信号传递给显示器去呈现画面。
好,然后这个硬盘,它这个模型里边并没有体现出来,它的硬盘其实放在这后边就是这个红线,他们接到了后边那个区域。
好,等把这个机箱组装完毕之后,我们再给机箱组装一些外设,整个电脑就算是组装完毕了。比如说有显示器,然后有这个键盘、鼠标,然后音响,这些都算是计算机的外设好了,然后关于整个这边的一个组成,我们就先说这么多,大家对这个结构有一个认识就 OK 了。
hello,大家好,那这个节我们来介绍一下程序运行的基本流程,注意这个我们只讨论基本流程,过分的细节我们在这里不做讲解。那么在上一个小节我们介绍了一下计算机的组成,然后并且还说了一下,把它放到机箱里边就可以去运行了。
那么其实当我们把所有的硬件全部都组装完毕,然后通上电之后,这个计算机它其实也不能够正常的去运转,去运行,因为它还缺一个非常重要的角色,叫做操作系统,我们常见的操作系统有这么三个,第一个是Windows,第二个是Linux,第三个是macOS。
那么操作系统是个什么东东?那么其实它们的本质也是一种应用程序,就是说它们也是01010101,那么这种程序它负责来调度和管理硬件资源。什么意思?我起来举个例子,比如说操作系统它可以决定让 CPU 去执行哪一个程序,再比如说这个 CPU 它可以对内存的资源进行管理,它可以创建,然后它可以销毁。那么其次操作系统也可以跟磁盘进行交互,比如说创建文件、删除文件等等,操作这些事情都是可以由操系统来完成的。
那么这里我们以 Windows 来为例,咱们平时会说装系统,那么装系统这个过程其实就是将操作系统这个程序安装到硬盘的这样一个过程,那等我们把这个操作系统程序装到硬盘之后,这个电脑就可以开机去运行了。
那么整个运行流程大体是这样的一个过程,首先它会先将 Windows 相关的一些程序文件先载入到内存里边,载入内存之后 CPU 就可以去运行了。那么在执行的时候,如果发现了有视频信号,就是需要在显示器里边去呈现,那就会交给显卡去处理,然后显卡处理完之后再交由显示器最后去呈现。那在处理过程当中,如果说遇到了声音信号就要播放了,这个时候会这个时候会交给声卡,然后声卡再把信号传递给外部的播放设备,比如说耳机,再比如说这个音响之类的,它们一结合就会成这样一个效果。好,这是我们简单说了一下操作系统它的一个启动的过程,也算是计算机它启动的一个基本过程。
那么在说完了操作系统之后,我们再来说一下应用程序。这里的话我们以英雄联盟来为例啊,其实不光是英雄联盟,那像咱们平时这个什么浏览器、 QQ 播放器这个流程基本上是一模一样子的。
那么我们要想玩这个英雄联盟,首先咱们得先下载,那么下载的话一般咱们从官网然后下载这么一个文件,下载完之后,然后我们就可以去安装,那么安装英雄联盟到哪,其实就是把它安到硬盘里边,我来给你演示一下把这个英雄联盟装到哪了?这里我来放桌面了,你看怎么来看?点击这个图标,然后右键我们点属性,点完属性之后大家可以看这有一个打开文件所在的文件夹,
咱们点一下,诶,点完之后你可以看一下这个就是现在英雄联盟所装的一个位置,我是把它装到了 d 盘的 WeGameApps 这个文件夹里边。
那么装到硬盘之后我们就要启动,怎么启动?就是双击那个图标就可以启动,然后双击图标之后它做哪些事情?我们来给大家说一下。
那大体流程跟那个操作系统的启动是一样子的,首先要先把这个英雄联盟的相关程序要先载入内存,载完内存之后,然后 CPU 去内存里边读取指令,然后执行指令执行的过程当中,如果说遇到了视频信号需要处理,交给显卡,让显示卡把信号传递给显示器去显示。那处理的过程,如果遇到的音频需要处理,会交给声卡,然后声卡处理完之后再交给这个 Web 的播放设备,比如说公放,比如说耳机,最后就有这样一个效果。
好,那么这是我们讲了一下程序运行的基本流程。
我们做一个小结,那这个程序它一般都是保存在硬盘当中的,注意这里用一个“一般”为什么呢?因为还有一些是存在这些软盘其他一些介质里边的。然后这个软件的安装过程,比如说安装什么QQ,安装什么这个 Chrome 浏览器,这个安装过程其实就是将程序写入硬盘的一个过程,然后这个程序在运行时,它会首先加载其内存。
程序在硬盘里边是不能执行的, CPU 也不能到硬盘里边直接取运行程序,它必须要载入内存,因为内存的操作速度是比较快的,当载入内存之后,然后 CPU 再从内存当中读取指令,然后执行程序。这是我们给大家讲了一下程序运行的一个基本流程,这时为后边的这个知识打一个基础。
我们来认识两个概念,第一个是进程,第二个是线程。
我们首先,什么是进程?我们首先可以通过字面儿意思去理解这个进程,可以理解为进行中的程序,比方说这里有一段儿程序,这一段儿程序的话已经载入内存了,然后我们的 CPU 开始执行它,那这个时候就会产生一个进程。
当然了我们还可以用另外一种方式去理解这个进程,这个进程是程序的一次执行过程,是一个相对来说比较抽象的概念。如果在 Windows 里边,或者说在 Linux 里边,它都给我们提供了一些方式来查看整个计算机里边的进程。这里我们以windows起来,通过任务管理器来查看计算机里边所有的进程,然后我们这里在下边的任务栏,然后右键可以看到这里有一个任务管理器,
然后点开它之后,你就会发现这里列出来很的东西,那这个东西其实就是进程。
当然有同学说我打开怎么是这样子的?别急,
可以点击详细信息,就可以列出来整个计算机所有的进程。
那比方说这里还可以去启动一个新的进程,我这里双击QQ,大家可以看一下,当我双击完 QQ 之后,它启动了QQ,这里就多了一个进程,那比方说我再来启动一个,大家可以看一下,它会再次去启动一个新的,
然后把这个关掉,京城少一个。然后我再关一个,京城又少一个,哎,当然了,进程这个关闭稍微有点延迟。
好,那么回过头来我们再接着继续来看另外一个概念。什么是线程?
这个线程是一个进程中执行的一个执行流,这个线程是一个进程中执行的一个执行流。可以看出来一个线程它是属于某一个进程的。
比方说大家看这里,我这里列了两个进程,在进程里面会包含它所有的线程。那这里要说明一点,就是一个进程当中它至少要包含一个线程,可以有一个,是吧?也可以有多个。
这个我们通过程序也是可以查看的。通过这个 pslist 这个命令,我来给大家演示一下子。
比方说这里我们找一个程序,比如说以火绒来为例,它的这个进程是进程ID,它的进程 ID 是9452,
打开 cmd,然后在这里输入命令,输入命令叫做 pslist,然后 -dmx 后边跟上这个进程ID,也就是9452,然后敲回车。
大家可以看到这里列出来好些内容,而这些内容就是是当前进程所启动的线程。好,当然了这里我要给大家,我需要给大家说一个说明,就是这个命令 pslist,你在你的电脑里边直接去运行,它是不好使的,不过这个相关的程序已经把它放到资料文件夹里边了,有兴趣的同学大家可以自个尝试去运行一下子。
这个并不是我们所学习的一个重点,大家只需要知道进程里面是包含线程就可以了。
好,那么下边的话我们再来给大家举一个例子,然后就是方便大家更好的去理解这个进程和线程。比方说开了一个蜜雪冰城的饮品店,这呢买应聘,那我这个店呢只要一营业一上班,那么整个店的运作它其实就是一个进程。
然而在这个进程当中就大家可以看到有很多员工,这个员工大家可以看一下,这个员工在洗水果,然后这个员工的话他正在接待用户,而这个员工他正在制作,他们三个人在做三个不同的事情,他们所做的事情是不同的线程,而线程在一起他们组合形成了整个的进程,然后为用户去提供服务。这就是进程和电程的一个关系。那我们讲这个内容的话,也主要是为后边内容做一个铺垫,大家知道了线程和进程之后,在后边的学习当中也会变得稍微的轻松一些。
我们来学习一下nodeJS 的 fs 模块,也可以把它称之为是 fs API。那什么是fs?它是两个单词的首字母的一个拼写,叫做 file system,意为文件系统。
那这个 FS 模块它有什么用?它主要是可以跟我们的硬盘进行交互,比如,比如说可以实现文件的创建、删除、重命名、移动等操作,还有可以完成文件内容的写入和读取,以及文件夹的相关操作。
不得不说这个 fs 是 nodejs 当中非常重要的一个模块,当我们在掌握了这个模块之后,在将来理解某一些程序时,就会变得非常非常的简单。那这个条件的话,我们来介绍一下如何使用 fs 来写入文件内容。
好,下面的话我们来实现一个效果。首先咱们先新建一个文件,叫做文件写入.js。
说一下我们的需求,打算去新建一个文件,文件名叫做座右铭.txt,并且往这个文件当中写入一段内容。写入什么内容?三人行,则必有我师焉,这是一个需求。
好,如何使用 FS 模块来实现这个效果?首先第一步我们要先导入 FS 模块。
如何来导入?我们可以使用 const fs = reqire('fs'); ,通过这样一个语法来将其导入。首先前边这个是一个声明符号,你可以使用const,也可以使用var,也可以使用let,这个都可以。
而这个 fs,它是一个变量的名称,这个名称你可以自定义,只要满足标识符的要求就可以了。不过绝大多数情况我们都会把它命名为 fs。而这个require,它是一个全局的函数,可以用来导入模块儿。这个名字你不能随便儿写,必须要写成require。而里边儿这个fs,它是模块儿的名称,你也不能随便儿写,必须要写成fs。
好,那么导入完毕之后,下边第二步就是写入文件,怎么来做?很简单, fs.whiteFile,括弧, white单词的本意是写入的意思,而 file 单词的本意是文件的意思。那么这个方法它接收这么几个参数,总共是 4 个参数,大家看一下,
第一个是文件名,就是你要往哪个文件里面去写内容,第二个是 data 数据,就是你要往文件里边x写入什么内容。第三个是一个选项配置对象,它是一个可选参数。第四是一个callback,是一个回调函数。
好,那么回过头来我们开始来写参数。第一个参数往哪儿写?我们打算往当前文件夹的这个座右铭.txt,往这个文件里面去写,这里有一个小点,就是文件如果不存在,它会帮我们自动创建文件并写入。好,那么这是第一个参数。第二个参数要写入的内容,三人行,则必有我师焉,这段话复制一份儿拿过来。
第三个参数是一个可选的一个配置对象,这里我们用不到,所以先不写第三个函数,直接写最后一个回调函数,这个回调函数它什么时候执行?是当写入在完成之后,就会自动去调用这个回调函数,并且会把错误传递给咱这个函数,你也可以认为是传递给这个error。
这里还有一个小点需要说明一下,就是如果说写入失败了,那么 error 它的值就是一个错误对象,如果失败了,写入如果失败,它的值就是一个错误对象。那如果说写入成功,如果写入成功的话, error 值就是一个null。当然这个形参名字你可以随便写, 不一定非要写 error 写 abc 也是可以的,
所以在这里我们可以判断这个 err 它的值,来去得知我们的写入到底成功还是失败,可以像这样子,哎,如果说 error 我们可以输出一下子来一个写入失败,那如果说他要是失败的话,他不是 null 他就会走里边。那如果说他要是 null 的话,他就不会走,这他就会走下边给你做一个输出 console.log 来一个写入成功。
好了,然后咱们来看一看效果怎么样?右键终端打开,然后 node 1杠table(自动补全) 敲回车。好,可以看一下此时座右铭.txt,这个文件就被创建出来了,并且内容已经OK。
那么其实到这儿之后,同学可能会困惑说,强哥你这是咋了?我们明明可以直接右键新建文件,然后往里边手动填内容保存文件就可以搞定的,为什么还要去写一个脚本,让这个脚本来费这么大劲去实现这个效果?这里我要解释一下,这是很多新人刚学习 nodejs 的一个困惑,就总感觉我右键就可以新建,然后并且也可以手动去完成这个事情。
下边的话我来举个例子给大家说明这个问题。比方说咱们做一个网站,用户来访问咱这个网站,
有一个需求,就是我希望记录用户每一次请求的时间,并且把它记录在一个文件当中。
那么你想一想,这个时候你如果安排一个单独的人在这,来一个人,然后你记一下,来一个人,你记一下,这个其实是非常非常不现实,因为人可能会比较多,有些时候的话可能成百上千甚至上万的人,然后一下子涌到我们这个网站。
你这里安排一个人,他可能忙都忙活不过来,但是我要拿脚本,下次就可以把这问题搞定。哎,你来个人我写一次,来一个人我写一次,可以很方便的实现自动化,所以这就是我们用代码去实现和手动实现它们的一个差异。
那么其次我还想再问一个问题,大家思考一下,就是我们平时打开编辑器,打开一个文件,然后编辑文件,编辑文件完了之后我们会保存文件,那保存完文件之后他又做了哪些事情?那么其实保存完文件之后,这个编辑器也是把这个信息写到了文件当中。
再说一个有意思的事情,这个 vscode 我们当时讲过,它也是借助于 nodejs 能力开发出来的,大家虽然说它是借助一个框架election,而这个 electron 它是借助于node 搭建起来的,那么 vscode 在写入文件时也是借助了 node 的 fs 模块实现的文件写入,
也就是说我们这里诶保存了一下子,它底层也用到了 fs 这个模块,是不是感觉很神奇?
我们来介绍一下 FS 模块儿工作的两种模式,一个是异步,一个是同步。
这两个概念连接起来稍微有一些难度,所以我们首先来举一个例子,比如说我跟李靖两个人打算去篮球场打会儿篮球,然后我们两个人商量好,然后出发了,出发走到一半,李靖突然跟我说她肚子怎么不得劲儿?难受的不行,需要去方便一下,然后转头就跑向这个卫生间了。
那么这个时候的话,我就面临一个问题,就是我等它还是不等它?我们首先的话先来看第一种情况,就是同步的情况,同步情况就是我会等他从卫生间回来,然后我们两个人在一起去篮球场,这是一个同步的情况。还有另外一种情况就是异步的情况是我不等他走他的,然后我走我的,那么哪种情况效率会更高?
其实异步的效率会更高,我不会在原地等待,然后直接进到篮球场就可以去打篮球了。但是我们在上学的时候往往会走同步的模式,就是会等小伙伴完事之后,我们再一起去回教室或者是去操场。
下边儿的话,我们在代码儿层面来给大家演示一下儿 fs 模块儿的两种模式。
其实我们上一个小节所写的这个 whiteFile,它就是一种异步的工作模式,
我们的代码自上而下开始运行,走到 whiteFile 这一块儿,它要进行磁盘的写入,而此时它会将这个磁盘写入操作交给另另外一个线程去完成。也就是说现在我们有两个线程,第一个线程是 js 的主线程来执行解析 js 代码。
第二个是我们的磁盘的写入,这样一个线程,我们把这一列线程统称为是 io 线程,输入和输出线程,
然后我们在启动这个线程之后, whiteFile 这个方法有一个特点,就是它是异步的,异步是不会等待这个结果回来的,它会直接向后去运行后边代码。
而这个 io 线程在写入完毕之后,它会将这个回调函数,大家可以看一下这个回调函数,然后压入到任务队列当中。
这一块内容跟当时讲的定时器是非常非常像的,就它会把这个回调函数执行这个任务压到队列当中,等 js 主线程,它再把这些初始化的代码执行完毕之后,然后就会从任务队列当中把这个回调函数取出来,然后再执行。
我们来举一个例子,比方说在下边做一个输出,简单一点儿, 1 + 1,按照我们之前常规的认识,代码儿都是自上而下按照顺序执行的,然后如果写入成功,它会先输出,写入成功然后再去输出这个 1 + 1 = 2。但是异步它的工作模式却不是这样子的,我们打开终端来看一看它的一个执行结果node,然后 1/ table ,大家可以看一下,它是先执行了第 19 行这个代码,然后再去执行这个回调函数。
其实就是刚才我们所说那样一个流程,当我们代码执行到这一块之后,它交给另外一个线程去进行磁盘写入,
主线程是不会等待结果,主线程直接去执行,后续代码,所以先走了 19 行的一个输出,然后等 io 线程在完事之后,它把回调函数加入到队列当中,然后等 js 主线程再把这些初始化代码全都执行完毕之后,它再去任务队列里边把这个回调函数取出来再执行。所以它这个输出结果先是 19 行的2,然后是 16 行的写入。
好,那么下边我们再来介绍一下 fs 模块写入文件的另外一种模式,就是同步写入。同步写入它是用的另外一个方法,叫做 whiteFileSync 这个Sync,它是同步那个单词的简写,
synchronize
而这个方法的参数跟 whiteFile 大致是一样的,只是没有最后回调函数这个参数。
好,我们来写一下,比方说咱写一个 data.txt,写什么内容?写一个 test 就是测试的意思,好,完事儿别的参数咱没有,下边去运行这个文件 ndoe 1 杠Tab,然后敲回车,
然后大家可以看一下,此时多了一个文件叫 data.txt,而我们写的文本也已经在里面了。而这个 whiteFileSync,它的工作模式跟上面这个 whiteFile 是不太一样的。代码自上而下开始运行,走到这儿之后, OK, io 线程开始去进行磁盘写入,这个时候主线程就停了,就不再往后执行了,而是等这个写完的一个结果,等它写完之后,我们的主线程再往后继续执行。
然后这两种模式哪个效率会更高?很显然这个异步的操作效率会更高一些,所以在针对一些对效率要求比较高的情景,都会选择用异步的这个方法,但是也不是说同步的 API 就用不到,比如说我们做一些自个的一些小工具,那还有就是对性能要求不是那么高的场景,都是可以用这个同步的这个 API 的。
而且同步 API 它还有另外一个好处是什么呢?就是它更容易理解,因为它整个的执行都是按照顺序一行一行去执行的,而不会出现让异步那样子回到函数,先执行这个,再执行那个这个场景。所以同步的 API 在用起来的话更容易让大家接受,可能这会儿你感知不出来,别着急,等后边再写一写案例时就能体会出来,其实同步 API 也是挺方便的。
好了,那这个节关于 FS 模块点入文件的两种模式,就先说这么多。
hello,大家好,我们来学习一下使用 fs 模块儿实现文件的追加写入。
这里我们会用到一个方法叫做 appendFile,appendFileSync。下边的话,来给大家演示一下这个效果。比方说我们在上一个小节往这个座右铭.txt 里边写了一段话,叫三人行则必有我师焉。
我们打算借助于第二个脚本,往这个文件的尾部再追加一段话,叫择其善者而从之,其不善者而改之。
好,那么怎么来实现这个效果?首先第一步我们还是要去引入这个 fs 模块, const fs 等于 required('fs'),那么第二步我们需要去调用方法,调用谁?调用 appendFile 这个方法。fs然后 appendFile 括弧,这个 appendFile 的参数跟 whiteFile 的参数是一样的。
第一个参数是文件路径座右铭.txt。第二个是要追加的内容,我们把这个文本加进来,“则其善者而从之,则其不善者而改之”,这是第二个参数。第三个参数是一个配置对象,我们用不到,直接去写第四个参数,就是这个回调函数,这个回调函数有一个形参,叫error,当然它名字可以随便写,它在追加完成之后会去执行,并且会接收到一个参数,那如果写入成功,他所接收到的值就是null。如果追加写入失败,他就会接收到一个错误对象。
所以这里我们可以做一个判断,如果说 error 它要是一个错误对象,它不是null,因为错误对象的话,强制转化为布尔类型的值是一个ture,我们就可以做一个输出,console.log 一个写入失败,然后 return 一下停止代码运行。那如果说 error 要是一个 null 的话,你想他肯定不走这,他会走下边就如果为 null 他写入成功,我们做一个成功提示,来一个“追加写入成功”好了,那么代码我们就已经写完了,写完之后我们来试一试,看看效果怎么样。
打开终端,然后 node 2- table(Tab自动补全) 敲回车。好,大家可以看一下效果,在这个 txt 的文件的后边就多了一段话,就是在这个则必有我师焉的后边就多了一段,
大家同学说少一个逗号,哎,少逗号这个好办是吧?我们可以把这段文本先手动把它删掉,删完之后咱们在要写的内容前边加一个逗号保存,这回来看看效果。摁上敲回车,大家可以看到这里就多了一个逗号,没有问题。
好了,这是一种写入方式,当然了,我们还可以呢,使用同步的那个 API 方法。fs.appendFileSync 括弧,然后第一个参数还是路径座右铭.txt。第二个是我们要写入内容,是温故而知新,可以为师矣。
好,大家注意观察,这是目前代码,这是我们这个同步写入的一个代码,我们来执行一下,试一试,来一个 node 2- TAB 敲回车。
大家可以看一下。在这儿的话就多了一段话了,
大家同学可能会说,这也没有换行,能不能换行去写入,这也是可以的,我们只要在写入的内容前面去加一个 br 标签就行,看大家很多同学可能会这样想。
我只是开个玩笑。
是不行的,在 fs 这个模块当中,如果你要想换行的话,需要使用的是杠 r 杠 n 哎,
用这个符号来去实现换行,不能使用 b r 标签儿。好,我们这回再来试一试,然后摁上敲回车。
好,大家可以看一下,这次它就会在新的一行来帮我们去追加一段文本好了,那么这是我们给大家演示一下文件的一个追加写入。
那么其实我再说一下,我们用这个 whiteFile 也是可以实现追加写入的。当然这个大家了解一下,比方说我们这里来一个 fs writeFile 括弧还是往这个座右铭.txt 去写一段话,比方说叫love love love。
好,注意看一下,我们只写这个,当然后边的回调函数我们得加上,来一个error,如果说 error 要是有值是一个错误对象,则表明是写入失败了,然后我们把代码停掉,否则的话我们提示一个写入成功。
那如果我们单纯这么去写的话,其实是达不到我们的目标的,大家可以看一下按上再回车,那么你会发现他会把我们的原来内容给清空,然后他去填写新内容,
我们希望追加怎么办?先 control z 退回来,
然后这个whitefile,它的第三个参数是一个配置对象,在这个配置对象里边有一个标识的属性叫flag,然后我们把这个 flag 的值设置为 a,也是可以实现 appendFile 一样的效果的,就是追加写的意思。
好,大家看一看,摁上敲回车,你看这三个 love 就在为师 后边,然后添加上了。
顺便我再说一下,我们什么时候会用到这个追加写入,就是需要持续的往文件里边儿写入内容,比方说日志程序的日志,我们可以用这个方法来实现,就是不停地往一个文件当中去写内容。再比如说那一次我们讲一个案例,就是咱们搭一个网站,用户来我们的网站访问,希望记录用户的访问时间和相关信息。在这个时刻我们也可以使用 appendFile 在文件尾部不停地追加内容。好关于文件的一个追加写入,我们就先说这么多。
hello,大家好,我们来介绍一下使用 fs 模块对文件进行流式写入,这里我们主要可以用到一个方法,就是 createWriteStream 这个方法来实现效果。
那么下边我们来说一个需求,于是我们打算写一首诗,这个诗的名字叫做观书有感,然后我们把这个诗的内容写到观书有感.txt 这个文件当中。
那如何来做?很简单,首先第一步先导入这个 fs 模块儿,这一步是固定的,导入完毕之后,下边第二步是创建一个写入流对象。 const 名字的话可以随便写,这里我把名字定义为 ws 等于一个 f s. createWriteStream。
首先先介绍一下这个方法的名字, create 单词的本意是创建的意思,而 write 单词的本意是写入的意思。 stream 单词的本意是流的意思。
这里它接收一个参数,这个参数就是文件的路径,我们写上就是这个观书有感.txt。 这个操作相当于跟这个文件建立一个通道,相当于是我们跟这个文件建立一个通道,什么时候想写就往这个通道里边。哎,去传入一个内容就可以了,
那怎么往这个文件当中去写入内容?这里就要借助于另外一个方法,它的名字叫做 write。好,我们可以这样来做, ws.write,写上半亩方塘一鉴开。哎,这是写一句,我们可以再调 write 写第二句,然后天光云影共徘徊。可以再写第三步,然后问渠那得清如许,最后一句为有源头活水来。
好,等我们把这个写完之后,最后一步就是这个,关闭这个通道,来一个 ws.close 保存好了。
那么下边儿的话,我们来看一看效果怎么样?右键终端打开,然后 node 3- table 敲回车儿,大家可以看一下观书有感已经有了,点开它,内容也已经在里边写入了,
但是没有换行,
没关系,我们可以在每一行的后边添加一个\r\n 这样一个符号,我们再重新写入一次敲回车大家可以看一下观书有感这个内容就已经是写入完毕了。
那么这种写入方式跟我们之前的 writeFile 它有什么区别呢?
我来说一下这种写入方式,它更适合于写入频次较高的场景。我来给大家举一个例子,你比方说强哥不会做饭,但是天宇老师他会做饭,比如说今天我想学习一个西红柿炒鸡蛋,然后我会给天宇打一个电话,打通电话之后我就问他,天宇你会不会做这个西红柿炒鸡蛋?他可能会跟我说他会做,我说这第一步该怎么做?他可能跟我说,强哥你先去那去洗洗西红柿,这是第一步。好,我去洗西红柿了。这个时候为了提高通信的效率,我会做一个事情,就是不挂断。什么叫不挂断?就是一个通道我不断开,然后我什么时候想问,我就再给他发一个,比方说下一步怎么办?他说打鸡蛋再下一步,然后放油,再下一步,比如说放这个鸡蛋,就是我们在建立通道的情况下,通信起来是非常非常方便的。而我们之前所用的这个writeFile,它就是一次性的。比方说我要写入文件了,好跟文件建立连接,然后写入完毕之后就断开这个连接了。你下次你再去调 writeFile,他还得跟这个文件建立连接,然后再断开。所以说这种流式写入它更适合这种写入频繁的场景,而 writeFile 它更适合这种写入频次相对较少的场景。
当然这里强哥还提到了一个流式写入,也适合大文件的写入,这个得结合后边的流式读取才能看到效果好。那么这里还有一个补充点,强哥想说一下子,就是关于 ws.close,就是这个 close 的方法,它的调用其实在这里是可选的,我们可以来看看效果。
比方说我手动的将这个文本去掉(注释掉),
然后我们去执行这个代码,敲回车执行完之后我们去看这个文件,你发现这块内容它也成功的写入了,这是怎么一回事?其实就是当它在当这个脚本在执行完毕之后,整个的这个资源也会被回收,这个通道也会被断开,而这个内容也已经写入完毕了,所以说这个 close 我们加也可以,不加也是 OK 的。
hello,大家好,我们来介绍一下文件写入的应用场景,顺便说一下将来我们在写代码时应该什么时候想到文件写入。其实文件写入这个操作在日常使用计算机的过程当中还是十分的常见的,只不过大家在平时用的时候并没有怎么在意。
比方说咱们来看第一个场景下载文件,这个下载文件它就用到了文件写入。比方说咱们用浏览器去下载一个视频,这个服务器会把视频的信息返回给浏览器,然后浏览器在接收到信息之后就开始往文件当中去写入,比方说强哥的电脑在下载文件的时候,就是把文件存到了下载这个文件夹里边,你可以看一下里边有图片,有安装包,是吧?还有视频之类的,这是第一个场景文件的下载。
其二是安装软件,这个安装软件它其实也是文件写入的一个过程。当时咱也看了一下那个英雄联盟,我是把这个程序装到哪儿了?装到了 d 盘的文件夹下边,可以看一下。那对于 QQ 和 QQ 音乐来说,他们的安装其实也是写入硬盘的一个过程,比如说 QQ 音乐,咱们可以来看一下,它也是存到了这个 d 盘下边。
好,那么再有就是关于这个保存程序的日志,咱说这个Git,以 Git 来为例,举一个例子,比方说这里强哥有一个文件夹,里边写了一些文件,我想的存档怎么办?右键 gate Bash 打开之后,
然后我开始 gate init 敲回车。你看当我们运行 git init 之后,这里就多了一个目录,
并且里边还多了几个文件,那这文件哪来的?其实文件也是由程序创建,并且往里边写入了内容的。
那再比如说我们打开命令行,做一次提交,来一个 git add -A,你看 git add -A 之后,他会把这些文件做一个存档,存到哪?存到这个 object 文件夹里边,你看这里边多了一些文件,那这些文件其实就是通过程序写到这个文件当中的。
再比如说给你举个例子,我们再来一个 gate commit -m 'init',
然后,然后敲回车,运行完成之后,大家可以观察一下,这里有一个文件,这个文件就是这个 logs 文件。
我们打开这个head,
大家可以瞅一眼,好,然后把它拿过来往这放,你看里边多了一段内容,并且这里还有一个什么INIT,
这个信息又是怎么来的?它其实也是通过文件写入的操作来实现的。
当然了我得说一下,这个文件写入它用的并不是 fs 模块,它是用来着人家程序的这个写入的功能。
好,这是一个程序日志的场景,它也用到了文件写入。
再比方说咱们通过编辑器去保存文件,这个时候也用到了文件写入,比如说咱们通过 vscode 打开一个文件,往里边放一些内容,比如说放一些分割线,写完之后我们 Ctrl s,你看我们一摁 Ctrl s,其实它就把这些内容写入到了我们那个文件当中,其实就是写到了这个文件当中。所以你看平时自个没有怎么多想,那底层,它确实是实现了文件的写入。
再比方说我们的视频录制也是用到了文件写入,大家可以看一下强哥的录完视频就把它放到了这个文件夹里边,然后这文件怎么来的?其实也是文件写入来完成的。
好了,那这咱们说了一下文件写入的场景,那么将来我们在写代码的时候,什么时候应该想到这个文件写入呢?
这里给大家总结了一个规律,就是当需要持久化的保存数据时,应该要想到文件写入。再说一遍,当需要持久化的保存数据时,应该要想到文件写入。这里我顺便补充一下,并不是每一次用文件写入都能够实现你的目标,但是你朝着这个方向去想是绝对没有错的。好了,那么关于文件写入的一个场景介绍,以及我们什么时候应该想到这个文件写入,我们就先说这么多。
hello,大家好,我们来学习一下使用 fs 模块儿进行文件的读取,我们主要会讲两个方法,一个是 readFile,另外是 readFileSync,
上边这个是进行异步读取,而下边这个是进行同步的读取。
我们来说一个需求,就是这里有一个观书有感.txt,这是我们之前已经写好的文件内容了。下边儿的话,我们打算通过 4-文件读取.js 这个脚本去读取观书有感.txt 的文件内容,
并且在控制台里边儿做一个输出,这是我们的一个需求。
好,那么下边的话,我们来看看如何使用 fs 来实现这个效果。首先要先引入 fs 模块儿,这一步少不了, const fs 等于一个 required('fs') 。下边我们先来一个异步读取, fs.readfile 括弧。
首先咱们先解释一下这个单词的含义,这个 read 它单词的本意是读取,而 file 单词的本意是文件,加起来就是读取文件。
然后这个方法它在调用时接收三个参数,有两个是必填的,一个是可填的,分别是文件路径,还有配置对象以及回调函数。这个配置对象是一个可选参数,这里的话我们用不到它,所以先不给它传参好,写两个参数。
第一个参数是文件路径,我们要读的文件是 观书有感.txt。好,然后下边儿回调函数,这个回调函数我需要单独说一下,它有两个形参,当然了两个形参名字你可以随便写,你这写a,这写 b 都没有问题。等他读取到文件内容之后,这个回调函数会被执行,并且会传两个值进来。这个 error 它用来接收错误的信息,这个 data 用来接收读取的文件内容。
好,这个 error 的情形其实跟我们之前写入文件那个是一样的,所以我们可以做一个判断,如果说要是一个错误对象,那我们就做一个输出,对不起,读取失败,然后我们写一个return。那么其次,如果 error 要是为null,表明它已经读取成功,我们就可以打印一下读取它的文件内容。
好,下面的话我们来执行一下。这个 js 文件。打开终端,然后 node 4- TAB 敲回车,大家可以看一下这个读取到的内容,它是一个buffer,
这个 buffer 具体长什么样子,我们可以把它转成字符串,用这个 toString 以 UTF-8 的编码方式对其做一个字符串转换,
按上撤回车,大家可以看一下,就读取到了,没有问题。
好,那么除了这种读取方式之外,还有另外一种读取方式,就是同步读取好 let data 等于fs. read FileSync 括弧,它接收一个参数就行了,就是文件的路径,来一个观书有感.txt。
然后可以console.log,打印一下 data 这个变量,这个 data 就是读取到的文件内容,不过它的值跟刚才那个一样,也是一个buffer,如果你想看一看字符串儿的形式,必须得在后边儿加一个 to string 好,然后我们摁上敲回车来看看结果,哎,没有问题。
好,那么其实到这儿呢,同学可能还是会有一个困惑,说这读取文件内容,我直接一点他不就看到文件内容了吗?这为什么我还要拿脚本去读取它呢?
这里我再解释一下子,跟咱们之前文件写入那个场景其实是一样子的,主要是为了实现自动化。我来举一个例子,比方说咱们将来做一个网站啊,用户通过浏览器来访问咱这个网站,打算要访问一下咱这个网站下边 1.html 的文件内容。那你想人家要访问这个你不能说,兄弟你等着,我去给你打开,然后我发,我再发给你,这个明显不现实,所以我们可以通过脚本来做。哎,用户的请求过来之后,由脚本去读取文件内容,然后读取完毕之后再通过网络把信息返回给用户的浏览器,就可以实现自动化了。所以说并不是大家想的那样子,我直接打开也能看到那个情况是不一样的。
我们来介绍一下读取文件的应用场景,这里列举了一些,当然并不是只包含这些,比如说第一个就是电脑开机,这个咱们当时讲过,电脑在开机之后需要首先将磁盘里边的内容先读取到内存当中,然后 CPU 才可以对程序去做一个运行,这是一个。
第二个就是程序运行,比方说咱们去运行这个游戏,是吧?咱们当时讲过这个东西诶,比方说我想运行一下英雄联盟,双击之后,它里边的程序相关文件就会载入到内存当中,其实也就是从硬盘当中,在硬盘当中将那些文件数据全都读出来,然后载入到这个内存里边去。
好,那么其次就是编辑器打开文件,举一个例子,打开编辑器,比方说我想看一看第五这个 js 文件,
我们点击一下右边儿就会出现这个内容,那么大家要思考这个内容是哪儿来的?那么其实这些内容都是从文件当中读取出来的,然后把它呈现到了这个位置,所以说在咱们平常不起眼的地方都有文件读取的操作。
那再比如说查看图片,播放视频,播放音乐,同样是如此,比方说咱们桌面儿有一张图片,打开可以看一下儿,那么其实也是一样子。
当我们双击之后,首先这个看图软件儿它会先运行,然后看图软件在运行之后会把这个文件内容读取出来,然后把里边的信息做一个解析,最后呈现在这个窗口当中。
好,那么再比如说 git 查看日志,这里的话,强哥也专门准备了一个仓库,咱们双击打开它,然后右键 GitBash 打开之后的话,我们去运行命令 git log 敲回车。
那之前接触过 git 的同学应该知道,这个命令是用来查看我们的历史提交记录的,里边包含了这个内容,什么时间等等一些信息。
那有一个问题,这些信息哪儿来的?平白无故生成的?并不是,它其实是到这个文件夹当中把一些文件里边的信息取出来,
取出来之后再在这个窗口去做一个呈现,是这么一回事儿。
好,再比如说上传文件,我们通过百度网盘,是吧?准备上传一个文件,那上传文件怎么回事儿?其实也是一样,它需要把本地那个文件的内容需要读取出来,读取出来之后再通过网络把这个信息传到服务端去。所以说上传文件的时候也会用到文件读取。
再比如说查看聊天记录,用过QQ、用过这个facebook的同学应该都知道,我想看看跟某个人的聊天记录,怎么办?哎,可以拿点击查看历史,是吧?就是查看聊天记录就会出现这样一个窗口,我们可以在里边看到历史的一些聊天记录。
那么这些聊天记录它在哪儿存的?有的当然不得不承认,有的是存储在云端的,存储在服务器,但是大多数聊天记录都是存储在本地的。我记得当时腾讯 QQ 还出了一个什么,出了一个这个会员的一个特权,就是如果你开会员的话,能够漫游,就是漫游所有好友的聊天记录,那个的话其实是保存在云端的。
那如果说你要是没有充会员,没有充会员这个聊天记录,它就只保存在你的本地电脑的文件当中好了,那么关于读取文件的应用场景,我们就先说这么多。
hello,大家好,我们来学习一下使用 fs 模块儿进行文件的流式读取。这里主要是用到一个方法,它的名字叫做 createReadStream。
那什么是流式读取?我们首先来介绍一下子,这里画了一个图,这个大黑框当然可以换个颜色,比如说红框,我们把这个红框假设为是一个文件。
流式读取就是读的时候一块儿一块儿的读,就是我先读一块儿诶,然后读完这块儿之后,我再读另外一块儿,读完之后我再读另外一块儿。它是这样子一块儿读的,而我们之前那个 readFile,它是一次性的把这些内容全都读取到内存当中,所以两者读取方式还是不太一样的。
好,下边的话我们来举一个例子,比方说我们有一个需求,这个需求是将资料文件夹当中笑看风云.mp4这个文件把它读取出来,载入内存。
好,那么怎么来做?首先第一步还是引入我们的 FS 模块儿, const fs = required('fs'),这是第一步。
下边第二步是创建这个读取流对象 const rs,当然了这个名字变量名你可以随便写,等于一个 fs.createReadStream。然后我们首先来解释一下这个单词什么意思,它是由三个单词组成的, create 是创建,然后 read 是读取, stream 是流的意思。
而这个方法它接收一个参数,就是要读取的文件路径,这个路径稍微注意一下,因为这个 mp4 文件和我们的 js脚本并不是在同一个文件夹下,所以我们要先回到上一级目录,再去资料里边找这个 MP 4 文件,所以路径我们可以这样去写,来个 ../ ,然后资料下边儿是笑看风云.mp4。好,这是我们的第二步,创建读取流对象。
第三步,我们要为这个读取流对象绑定一个 data 事件,这个 data 事件用来获取读取到的数据,怎么来做?就是 rs.on,注意绑定事件,这里你要用 on 方法,然后里边这个事件的名字叫data。
第二个参数是一个回调函数,这个回调函数有一个形参,名字叫trunk,当然这个名字可以随便写,官方是把这个名字起成了trunk,这个 trunk 单词本身有块,有大块儿的意思。
好,那么下边我们来说一下这个回调函数它什么时候执行,就是当从文件当中读取出来一块数据之后,就会执行一次回调。刚才我们说了,流式读取就是一块一块的读,我先读一块,我再读一块,而这个回调函数是每当读完一块之后,就会执行这个回调函数,并且会把读取到的内容传递给这个行参,让他去执行,让他去处理。
所以我们在这里可以使用 console log 来打印一下 trunk 这一个变量。
好,那么下边我们来运行一下这个 js 文件,打开终端,然后来一个node,这是 5 杠 table(自动补全) 敲回车,大家可以看一下,控制台就输出了很多的buffer,这就是我们从文件当中所读取到的内容。
那同学可能会有一个困惑说,那我每次从文件当中读取的究竟是多少数据?
这里的话,我们可以通过一个 length 属性来获得这个 buffer 的长度,我们来看一下,再来摁上敲回车,大家可以看到这个值是65536,那么这个 65536 它的单位是字节,而这个 65536 字节对应的是 64 k b。也就是说这个流式读取,每一次在读的时候,它会从文件当中读取出来 64 kb 的数据。大家同学可能会说,那为什么最后这个是118?这原因很简单,就是因为最后那一块不够了,不够 6536 了,就把剩余那个数据给读取了出来。
当然有同学可能会说,那我想看看这个 buffer 里边长什么样子,我来个 toString 行不行?这里我解释一下,这样做是得不到你想要的结果的,大家可以看一下,这是一堆的乱码。
为什么会乱码?这是因为在文件当中,或者说在这个 MP4 文件当中,它存的是视频的信息,它并不是大家所想的 UTF- 8 的字符信息。所以你强行把这个视频信号转成字符信号,我们是看不到一个正常的文字结果的,这是我们补充了一下这个点。
其次我们再说一个点,就是当我们的读取流在把内容读取完毕之后,它还会触发另外一个事件,这个事件的名字是end,我们写一下 rs.on,然后and,当然了这个 end 事件它是一个可选的事件,并不是说一定要绑定这个事件。给大家补充一下,这个点为后边也是做一个铺垫。
好,我们来做一个输出来读取完成保存,我们来看效果怎么样,然后摁上敲回车,然后结合上面这个(data事件),结合上面这个,然后摁上敲回车。
大家可以看一下读读读读读,读完之后,这里执行这个回调函数,显示读取完成,然后同学可能会想说,那这种读取方式它有什么用?
给大家说一下这种读取方式,在读取大文件或者处理大文件的时候,它可以提高整体的效率,当然在这个案例里边大家可能体会不出来,唉,怎么就提高效率的啊?这个大家先不要着急,等下一个小节我们做一个练习啊,结合一下子你就能感受出来,这个流式读取,它确实是可以提高读取的效率的。
hello,大家好,我们来做一个 fs模块儿的练习,打算来实现一个复制文件的效果。我们来看一下需求,我们的需求是打算复制一下资料文件夹当中的笑看风云. mp4 这个文件,
下边儿的话,我们首先用第一种方式来实现一下子,这是方式一,准备用这个 readFile 来做,那这个文件复制其实逻辑很简单,就是把这个文件的内容我们取出来,取出来之后再写到另外一个文件里边就完事了。
那我们先做第一步,就是读取文件的内容。 let data,然后等于一个fs,当然我们这里没有导入,先把它导进来。 const fs 等于一个 required('fs')。下边这里我们 fs.readFileSync。为了方便我们这里使用同步的 API 。路径这一块的话,文件和 MP4 文件它们两个并不在同一个文件夹下,所以说我们这里写相对路径,点点杠,然后资料下边是笑看风云.MP4。
读取完文件之后,下边儿做第二件事情就是写入文件。好,我们来一个fs.whiteFileSync 往哪个文件里边儿去写,我们准备来一个笑看风云2.mp4。好,那路径的话我们可以这样去写,我这一份儿拿过来(资料文件夹中的笑看风云.mp4路径),这儿来一个杠2。好,这是文件的路径。
第二个参数是要写入的文件内容,我们把读出来的数据拿过来放在这,然后把这个数据写到新文件里边就可以了。
好,下边我们来看看效果怎么样。右键终端打开,然后 NODE 6 杠 TABLE 敲回车。大家可以看一下这个笑看风云 2 就来了,
我们可以呢打开这个文件夹,去看一看这个 2 能不能播放,双击打开。
好,好,大家可以看一下,这个是没有问题的,复制已经成功了,
这是第一种方式,我们用 readFile 来做。
下边我们用第二种方式来实现一下子,
方式二,用这个流式操作,那流式操作这个思路也是一样子的,就是要读,读完之后再写。
我们首先创建一个读取流对象, const rs 等于 f s.createReadStream 那个文件的路径,,我们复制一份儿,把它读出来。
然后我们再创建一个写入流对象, const ws等于fs.createWhiteStream。这次我们来一个笑看风云三.mp4 就是往这个文件里边去写内容。那两个流对象我们在创建完之后,接下来我们开始读一点,写一写,
为这个读取流绑定事件,绑定 data 事件,然后来一个 rs .on data 回调函数放进来,每读一点儿,我们写一点儿,来一个ws.white 括弧儿,把这个 trunk 放进来。
好,那么接下来的话,我们一起来看看效果怎么样?直接按上敲回车,大家看一下这个笑看风云 3 已经出来了,我们点开文件夹去看看效果。好,大家可以看到这个视频,也是可以播放的。
说明一点,我们这个写法也是 OK 的,那么两种写法哪一个会更好一些?其实第二种方式(流式操作)它会更好一些,它所占的资源会更少一些。
为什么呢?咱们来捋一捋这个思路,你看咱先说第一种方式,它是把这个文件的所有内容全部都读到了内存当中,然后再把这个数据写到文件里边。那这个文件多大?这个文件 70 多兆,大家可以看一下 77. 8 兆,也就是说它至少要占据 77. 8 兆的内存空间。
然后但是我们来看看第二种方式,是流式的读取与写入,这种方式的话它的理想状态。注意我说的是理想状态,它只需要 64 kb 的内存空间就可以完成文件的赋值,因为它每次它只取64,然他往这个文件里边去写,取一点写一点,所以说他理想状态下只要有 64 KB 的内存空间就可以完成这个复制。
那为什么说它是一个理想状态是 64 k b,这是因为我们的读取速度一般都会比写入速度要更快,所以它把数据读取过来之后往里边写,还没等他写完第二块数据就读进来了,所以就会形成这么一个局面,这里换一个方式给大家来演示。
比方说我们打算往这个文件里边去复制(左边图示文件复制到右边文件),那么这种读取流的方式就是它读一点儿,然后写一点儿,读一点,写一点,然后读一点写一点。当然这是一种理想状态,它只占这 64 KB 的空间,但是我们说了它这个读取速度一般都要比这个写入速度要快,虽然等这个还没写完,另外一个就过来了(内存读入了多块),他这个去写入这个读,然后这个读这个读,是吧?这个写,所以理想情况下是 64 kb,但是即使是这样子,它所占的内存空间也比整个文件的内存空间要小。
给大家写一个代码演示一下子,我们这里去导入一个模块儿叫 process required 一个process,这个 process 也是 nodeJS 的一个内置模块儿,我们可以通过它里边儿的一个方法来获得代码儿运行的一个内存占用量。
好,我们先看上边儿这个代码儿的内存占用量,把它复制一份儿,然后我们这里输出一下子叫process.memoryUsage() 。
memory 的话,单词的本意是内存的意思,而 usage 是使用量的意思。
好,我们来看看效果怎么样。按上敲回车,这一块给的是一个对象,然后这里我说一下,你不必去看所有,只需要看这个 rss 就行了,这个 rss 是整个占用内存的一个大小,它这的话是 110 什么什么滴,我们拿过来,单位也是字节,我们可以来算一算这个数字它对应的这个内存到底是多少?好,然后 control v,然后除以一个1024,等于,这是一个KB,我们再除以一个1024,等于这是一个兆B,所以这个代码运行完之后,它整个内存占用是 105 兆B 的内存
105MB
好,我们把这代码注释掉,来看一看下边这个代码它的一个内存使用量。
把这个代码咱打开之后,我们不能在下边去直接去写,为什么呢?因为这代码还没有执行,这是一个异步的回调,所以我们正确方式应该这样去做,rs.on end,等把这个文件都读完之后,再去统计这个脚本它总共消耗的内存的这个资源。
好,我们把代码复制一份,然后拿过来放在这,然后摁上敲回车,看看这次它的内存占用量是多少。好,把它复制一份。拿过来,拿过来之后咱们也算一算,然后打开它粘过来,然后除以 1024 等于,这是个KB,再除以 1024 等于这是一个兆b,它总共的这个内存占用的话是 41 兆。
大家可以看一下这两个脚本,我们两种方式,它们的占用内存空间是它的 105 - 41,就是 60 多兆的内存差距。那我们是通过这种方式给大家对比了一下两种代码两种写法的一个差异,那明显是流式这种操作,它占用内存空间会更少一些。
好,那么其实这里还有一个简便操作,来给大家演示一下子这种写法,还有一个简便写法,就是我们可以直接这样做, rs 读取流,直接调这个 PIPE 括弧里边儿跟一个ws,用这种方式也可以快速实现这个复制。
这代码什么意思?我们先看一下,读取流,把这个数据读取完之后交给写入流,交给写入流,这个 pipe 单词的本意是管道的意思,管道就是我这儿把东西取出来,通过管道传递给你,这个写入流是这么一个意思。当然了,这个内容的话,我们用的并不是特别多,所以大家以后看到这个代码知道是什么意思就可以了。
hello,大家好,我们来学习一下使用 fs 模块对文件进行重命名和移动。
那主要用到一个方法叫做 fs.rename,当然了还有一个同步的版本叫 fs.renameSync。下边我们来实现一个效果,就是在此之前我们写过一个座右铭.txt 这样一个文件,现在我们想通过脚本将这个文件的名字改成论语.txt。那么下边儿我们通过代码儿来实现一下子。
首先第一步的话,我们还是要导入这个 fs 模块儿。const fs,然后等于一个 required('fs')。
第二步就是调用 rename 这个方法。先说一下这个 rename 它单词是什么意思? rename 单词的本意就是重命名的意思,
然后这个方法它接收三个参数,
第一个参数是文件的旧的路径,你也可以认为是要修改的文件路径,我们这个文件是座右铭.txt,我们写成相对路 ./杠左右铭.txt。
第二个参数是这个文件的新路径,我们把它定义为是论语.txt 也在当前文件夹下。
第三个参数是一个回调函数,而且只有一个行参error,它的这个特点跟之前写入文件和读取文件是一样的,我们这里可以做一个判断,如果说有错误,我们来一个提示,来一个操作失败,我们 return 停止脚本。反之我们来一个提示,来一个成功的提示操作。
那么代码写完了,接下来我们运行一下代码,右键终端打开,然后 node 空格 7 杠 table 敲回车,大家可以看一下,这个时候我们那个文件名就由那个座右铭改为论语.txt,这是一个重命名的操作,
除了可以进行重命名之外,这个 rename 还可以进行文件的移动。我们来举一个例子,
比方说我们想把这个 data.txt 移动到资料文件夹当中,可以看一下资料文件夹里边目前是没有这个文件的。
好,我们写一下 fs.rename,旧的文件路径是点儿杠 data.txt,新的文件路径是 ./,然后资料下边儿的 data. txt。注意这个名词不能省,你不能说我光写一个资料这样子做是不可以的。好,然后第三个参数还是那个回调函数,这个代码跟上边是一样的,所以我们直接把这个代码 copy 一份拿过来。接下来我们看效果怎么样。
来一个 node 7 杠 table 敲回车,大家可以看一下这个文件刚才还在这儿,现在就到了资料这个文件夹下了。
其实重命名和移动文件的本质是一样的,都是在更改文件的路径。大家同学可能会说,那我什么时候会用到这种操作?我直接拖拽不就行了吗?这个情况其实跟之前的文件写入和文件读取是一样的,就是为了实现自动化,比方说咱们要做一个网盘用户,希望把这个文件名改一改,不能说我后边安排一个程序员手动的帮用户去更改文件名,或者说去移动文件,这明显是不太现实的,我们终究还是要借助于脚本自动化的完成文件的重命名和移动。
当然了,这个 rename 它还有一个同步的版本,就是这个 renameSync,这里的话我们就不再演示了,它的特点跟原来的同步异步特点是一样的,就是没有这个 callback 回调函数而已。
hello,大家好,我们来学习一下,使用 fs 模块对文件做一个删除操作,这里我们会用到一个方法叫做Unlink,待会儿的话,会给大家再补充一个删除的方法。
首先我们先举一个例子,比方说我们想把观书有感.txt 这个文件把它删掉,怎么来做?首先第一步还是要导入我们的 fs 模块儿, const fs 等于 required fs 。第二步是调用这个 unlink 方法, fs.unlink 单词的本意是链接,而 unlink 是取消链接的意思。
好,然后它里边儿其中两个参数,
第一个参数是删除的文件路径,
第二个是回调,咱那个删除的文件是观书有感在当前文件夹下,所以我们写成点杠,观书有感.txt。第二个参数是一个回调函数,跟之前的回调是一样的,先做判断 console.log,如果说出现问题,我们提示删除失败,然后顺便 return 停止代码,如果说要没有错误,我们提示一个删除成功。
好,下边我们来运行一下第八个 js 文件,来一个 node 8 杠 table 敲回车,大家可以看一下那个观书有感.txt 就被删掉了。
那么除了这个 unlink 可以删除文件之外,它还有另外一个方法也可以删除,就是这个 rm 方法。这个方法是 node g s 14.4 版本所引用的一个新方法,我来给大家演示一下子 fs.rm,
那这个参数跟上面儿个参数是一样的。
然后第一个参数也是要删除的文件路径,这次的话我们把论语.txt 把它删掉。
第二个参数跟上面那个参数是一样的,也是一个回调函数。为了方便直接把代码复制一份,然后把注释去掉,格式化一下子。好,我们来看看效果怎么样。摁上敲回车,大家可以看一下这个论语.txt 就被我们给成功的删掉了。
那顺便补充一下 unlink 和 rm 有什么区别呢?它们都有自个儿对应的同步的方法,上面这个叫做 unlinkSync,下边这个叫做rmSync。
同学可能还是会有这个困惑,就是说我明明可以右键删除的,那为什么我还要用脚本去删除呢?主要还是为了实现自动化,比方说做一个网盘系统,然后用户说我要删这个文件,你不能说我给你安排个人儿是吧?手动去给你删除真不现实。再比如说用户可能会删除一些缓存,删除一些缓存文件,那我们也可以通过脚本自动化的来完成这些文件的删除。
我们来学习一下使用 fs 模块进行文件夹操作,这里我们主要会学习三个操作,
第一个是创建文件夹,
第二个是读取文件夹,
第三个是删除文件夹。
下边的话,我们首先来看第一个就是这个创建文件夹,我们打算在代码这个文件夹下创建一个 html 的子文件夹。
怎么来做?首先第一步要先导入 fs 模块,这个是固定的, const fs 等于 required fs 。第二步我们开始去创建这个文件夹,来一个 fs.mkdir,这个 mk,它是 make 单词的一个缩写,意为制作 dir 是 directory 单词的缩写,意为文件夹,加起来就是制作文件夹的意思。
而这个方法它接收三个参数,第一个是文件夹路径,第二个是配置的对象,第三个是一个回调函数,这个配置对象它是一个可选参数,咱们这儿目前还用不到,所以就先不传它了。
好,写第一个文件夹路径 ./html,因为是要在代码这个文件夹下跟咱这个 js 文件是统一的,所以说我们写一个点杠。好,那么第二个参数咱不用直接写第三个参数, error 做一个判断 if error,然后 console.log 打印一下子,就是创建失败,并且用一个 return 停掉代码,然后如果说没有错误,我们 console.log 来一个创建成功。
OK,搞定下边的话,我们来运行一下这个 js 文件,来一个 node 九杠 table 敲回车,大家可以看一下,这个目录就创建成功了。
那除了可以创建这个单个文件之外,它还可以进行这种递归的创建。
来给大家演示一下子
2- 2 递归创建:
怎个递归创建法?比方说我们在这个代码文件夹下边想创建一个 a 目录,然后 a 目录里边想直接创建一个 b 目录,然后 b 目录里边直接创建一个 c 目录,打算这么去做。
好,我们首先把代码 copy 一份拿过来,然后路径咱们稍微调一下,这我写个a,然后斜杠b,斜杠c,这是咱们所谓的递归创建,咱们先试一试,看直接创建行不行?然后按上返回车,大家可以看一下,这里显示创建失败,也就是说你直接这样去递归创建是不可以的。
要想递归创建怎么办?我们需要添加第二个参数,就是那个配置对象加一个属性叫 recursive。
这个 recursive 什么意思?它是递归的意思,就是表明我要递归创建,创建完a,然后去创建b,创建完b,然后去创建c,好,我们来试一试。摁上敲飞车,大家可以看一下这个a、b、 c 三个目录就创建成功了。
同学说,这个展示的效果跟我预想的不一样,能不能把它们竖着去排列显示?
这个是可以的,我们点开齿轮儿,点击设置,在这里可以去搜这个compact,搜完之后大家可以看第一个选项,控制资源管理器是否以紧凑形式呈现文件夹,
他说对 Java 包结构非常有用,咱们这写前端的,所以说咱们把这个勾取消,把这钩取消之后你就会发现
我们这个文件夹的这个折叠情况就没有了。
好,这是关于文件夹的两种创建,一个是创建单个,一个是递归创建。
下边的话我们来说一下关于文件夹的一个读取,这里我们来个 2 - 3 读取文件夹,比方说我们想看一看资料文件夹当中都有哪些资源,怎么办?
我们可以这样来做, MS 点 readdir,这个 read 单词的本意就是读取的意思,而 dir 咱们说过它是 directory 单词的缩写,是文件夹的意思。
而这个方法在调的时候,它接收两个参数,第一个是路径,咱们写成 ../资料。第二个参数回调函数,这个回调函数它有两个参数,一个参数用来接收错误信息,一个参数用来接收读取到的文件夹内容还是一样,我们做判断,如果 error 做一个提示,读取失败,然后写一个return,那如果说没有错误,我们打印一下 data 它的一个值。
好,我们来看看能不能读取到资料。好,那么接下来我们来运行一下子,摁上敲回车。好,大家可以看一下这个返回结果,它是一个数组,而数组里边的内容是这个文件夹当中资源的名称,
所以通过这个方法可以得到目标文件夹当中资源的名称列表。
好了,这是关于读取操作,当然了,这个读取并不说只能读取它,其实任意一个文件夹咱们都是可以读取的,比方说我读取当前文件夹可不可以?诶,这也是 OK 的,比方说这里写一个./,然后摁上敲回车,
大家可以看一下当前文件夹的这个文件列表,我们也是可以获取到的。
好,这是 readdir,那么下边还有一个操作是删除根据夹, 2 - 4 是删除文件夹,比方说咱们现在想把 html 这个文件夹给它删掉怎么办?那是 fs. rmdir 括弧,这个 rm 它是remove单词的一个缩写,意为移除。dir,它还是文件夹的意思。然后里边两个参数,第一个是要删除文件夹的路径 ./html,第二个参数是回调函数,它只有一个形参,那这里还是一样 error 提示,来一个删除失败,然后 return 代表儿这里我们输出一下删除成功。
好,我们来尝试运行一下这个代码,大家可以看一下,这个 html 就被删除了,
那如果我们删完之后再去运行,还能不能行?来试一试,按上敲回车,它显示删除失败,为什么呢?因为这个资源它找不到了。
好,这是一个单个的删除,那么下边我们再来说一下这个递归删除,复制一份,然后拿过来把上面的代码注释掉,这是一个递归删除。
比方说我想把这个 a 删掉,注意看,我想把 a 删掉,注意,我只写了a,没有去写这个b, c 路径就是我希望它能够自动帮我把 a 这个文件夹整体都删掉。咱们先看看行不行,然后摁上敲回车。大家可以看一下,这是一个删除失败,
那错误的信息是什么呢?咱们可以把这个 error 的对象打印一下。
好,然后摁上跳回车,大家可以看一下这个,这是那个 error 的值,它是一个对象。然后前面这个中括号它是一个错误的信息,说了这个 directory not empty,他说文件夹不空,不是空的,不让你删。
那么这个时候的话,我们可以借助于一个配置对象来完成递归删除,那这个值跟刚才递归创建那个是一样的,也是 regular SIM,来一个处保存好,我们来看看效果怎么样,然后摁上敲回车。
大家可以看一下这个时候那个 a 目录就被我们已经给删掉了,但是它这里有一个提醒说了 deprecation warning 是一个不赞成的提醒,若in future versions of nodejs 在将来的 nodeJS 版本当中, fs. rm recursive true will be removed,意思就是说这个东西将来会被移除,那他说建议你使用 fs.rmdir 去删除,意思就是说不建议你一直使用这个了,它将来会被移除。
然后建议你使用这个rm,这个 rm 咱们当时在删除文件时也是使用过,我们来演示一下怎么做好,然后建议使用,然后上边rmdir这个是不推荐使用。
好,然后我们这里新建一个文件夹,手动建,来个 a(文件夹),a 里边的话我再建一个 b (文件夹)里边,我再建一个c(文件夹)。
然后接下来咱们使用 rm 删除,看看还有没有这个警告,然后摁上敲回车,大家可以看一下删除成功没有问题,也没有那个提示的警告了。
好了,那关于文件夹的这个三个操作,一个是创建,一个是读取,一个是删除。
我们来学习一下使用 fs 模块儿来查看某一个资源的状态。这里主要是会用到一个方法,它的名字叫做 stat 这个 stat 它是单词 status 的一个缩写,意为状态。
下边我们通过代码来给大家演示一下这个方法的使用。比方说我们现在想查看一下资料里边儿笑看风云,点儿 MP4 这个文件的状态怎么来做?首先第一步要先导入 fs 这个模块儿, const fs = required fs。第二步是调用这个 stat 方法,我们说过它是 status 单词的一个缩写,意为状态 fs.stat 里边儿我们传两个参数,第一个参数是文件的路径,./,然后资料下边儿的笑看风云.mp4。
第二个参数是一个回调函数,这个回调函数有两个参数,第一个参数是错误的对象,第二个参数是文件的状态信息,它也是一个对象。好,我们还是按照之前的方式一样,然后先判断一下是不是有错误,如果说有错误的话,来一个提醒,操作失败,然后通过 return 来停止代码继续执行。如果说没有错误的话,我们来输出一下这个读取到的状态信息。
好,那么接下来我们打开终端去运行 10 这个 js 文件,来一个 NODE 10 table 敲回车,大家可以看一下,这里并没有提示操作失败就意味着我们操作已经成功了。
然后 data 的输出结果是这样的,一个对象里边包含了很多的信息,我们来看一些对于我们来说有用的一些信息。比方说这个 size 是来查看文件的大小的,
然后还有一个是最后这个 birthtime,是这个文件的创建时间。
然后还有一个是这个 atime 是最后的访问时间, mtime 是最后的修改时间。还有一个是 ctime,这个是最后一次修改文件状态的时间,是关于文件的一些相关的信息。
那么其实除了这个之外,我们还有一个操作,就是可以获取文件的类型。这里有两个方法,第一个方法是这个 isFile,用来查看这个资源究竟是不是一个文件。好,我们来输出一下子data.isFile,我们来看看效果怎么样,然后摁上敲回车,可以看一下这个返回结果是一个true,由此表明这个目标资源是一个文件。
当然还有另外一个是 isDirectory,用这个方法可以检测目标资源究竟是不是一个文件夹。好,写一下 isDirectory。好,我们摁上敲回车看一下,看第一个是一个true,第二个是一个false。好,是我们通过 stat 去查看了一个资源的状态。
那么其实这个方法我们用的并不是特别的多,为什么讲这个?来给大家展示一个东西,我们打开资源管理器,这是代码文件夹的一个显示的内容,这是文件名称,创建时间,修改时间类型还有大小,你会发现这些信息我们通过 fs 模块都是可以获得的,前面这个(左边框选)我们可以通过 readdir (右边框选)来获取,而后边这些我们可以通过 stat 去获取。
所以说将来我们可以通过代码来获得这个文件的所有的信息,不用这个资源管理器也是可以实现的。
我们来介绍一下 fs 模块当中的路径,这里的路径主要有两种写法,
第一个是相对路径,
第二个是绝对路径。
当然了,这个路径我们在之前学习 html, css 的时候已经是接触过一些了,不过那个路径跟这个路径还是略微的有点不太一样的。
来给大家演示一下子,举个例子,比方说我们打算在代码文件夹下新建一个 index.html 的文件,那么该怎么做?首先第一步,我们先导入 fs 模块儿, const fs 等于一个require fs。首先先给大家演示这个相对路径, fs.whiteFileSync 括弧,我写成 ./index.html,写入内容,我们来一个 love 保存好,我们接下来运行 11 这个 js 文件,来个 node 11 table 敲回车。
好,大家可以看一下这个 index 点 h t m l 我们就创建成功了。这是第一种相对路径的写法,那么下面我们来演示第二种写法,可以不写点杠,这个不写点杠的效果跟跟上面这个写点儿杠的效果是一样的,都是在当前文件夹下去创建这个文件。好,我们来试一试按上敲回车,大家可以看一下它还是在当前文件夹下去创建的 index.html。
好。那么下边的话,咱们再来看一看另外一种相对路径的写法,那就是点儿点儿杠上一级目录。好,我们来按上敲回车看效果,你会发现这次他就把文件创建到了外层文件夹下了。
这强哥顺便补充一下这个相对路径,不是说只有这个 writeFileSync 才可以使用,我们之前那些操作都是可以的,比方说读取文件、流式读取、流式写入、删除等等操作都是可以用这个相对路径的,当然了绝对路径也可以。
那么接下来我们来演示一下这个绝对路径,那么绝对路径呢?这里也是有两种写法。首先我们先来看第一种写法的话,是以盘符开头,然后去编写路径,比方说这里我写一个 d 盘 index.html,写入文件内容还是love,那这个代码的意思就是说,我们打算在 d 盘的这个文件夹下去新建一个文件叫 index.html 并且写入内容。
好,我们来测试一下,看看效果怎么样。把这个文件夹放到右侧好,然后摁上敲回车,大家来看效果,你会发现这个 index.html 就创建成功了,
这是 d 盘开头的这种写法,同学说我写 c 盘开头行不行?这个写 c 盘其实是不行的,为什么呢?不是路径写法有问题,而是权限不够,我们来试一试,大家可以看一下。
这里报了一个错误,说 operation not permitted,就是说你这个操作不被允许,主要还是因为权限好。
那么下边的话,我们再来演示一下绝对路径的另外一种写法。这种写法其实是在 Linux 系统下边用的比较多, Windows 下边用的相对来说比较少,
就是以斜线开头,那么你以斜线开头这种路径,它也是在这个盘符的根目录下边,也就是最外层文件夹下再创建文件,我们来看效果。,然后摁上敲回车,大家瞅一眼这个 index.html 也是可以创建出来的,没有问题。
好,那么这里我再说一下,那么这个路径它其实是一个相对来说比较繁琐的知识点,因为它的情况比较多,既有 url 的路径,又有fs,也就是文件系统的路径。所以很多同学在编写代码的时候,对于这个路径相对绝对,始终是搞不清楚。大家不用担心,到后边会花一个章节,把 url 里边的路径和这个文件系统路径再结合起来给大家讲一下,相信到那个时候大家能够对这个路径有一个更为清晰的认识。
我们来演示一下 fs 当中相对路径的一个bug,顺便说一下这个 bug 应该怎么去解决,还是以上节课那个案例来接着继续。我们还是希望在这个代码的文件夹下新建一个文件,名字叫index.html。首先的话,我们先把 fs 导进来,然后第二步去创建文件,并且写入内容, writeFileSync 写一个相对路径,./index.html。内容的话,我们还是选择用 love 好,我们去运行一下子右键终端打开,然后 node 12 table 敲回车,大家可以看一下,此时它是可以把这个文件创建出来的。
接下来的话,来演示一个操作,就是我打算切换一下命令行的工作目录,注意我运行的一个 cd..,将工作目录切换到了外层,也就是这个 12-fs 文件夹下(原本在 12-fs/代码 )。那么这个时候我还能不能直接去运行这个命令呢?我们来试一试当回事儿,大家可以看一下,他报错了,说 cannot find the module,巴拉一大堆,他说找不到这个模块儿,
其实很正常,就是因为在 12 这个文件夹下根本就没有 12-相对路径的bug.js 这个文件,所以他找不到,他也没办法去运行这个脚本。
那我们在这个文件夹下能不能去运行这个 js 文件?答案是可以的,不过你要把路径写对怎么写?我们可以这样去写, node 来一个 ./代码,我们到代码文件夹下去找这个 12 的 js 文件,敲回车,大家可以看一下,是可以运行的,但是这个代码运行结果有点出乎我们的意料,我们是希望在代码文件夹下边去创建,但是他这次却把文件创建在了 12-fs 这个文件夹下。
这是怎么一回事儿?我们需要解释一下,就是 fs 模块儿当中这个相对路径,它的参照物,它是比较特别的,它参照的并不是这个 js 文件的所在目录。再说一遍,这个相对路径,它参照的并不是这个 js 文件的所在目录,而是参照的命令行的工作目录。也就是说你这里写一个点儿杠当前目录,那这个当前目录指的是命令行的工作目录。
下边儿的话我再来给大家说明一下子,你看咱们去运行这个代码儿,那这个点儿杠当前目录,它是跟命令行的工作目录挂钩儿的,也就是说咱在这个 12-fs 下的,然后你这里跟了一个 index.html,所以它在创建文件时就会在 2-fs 是这个文件夹下去给你创建。来,我们再试一次,把这个删掉,然后摁上敲回车,可以看一看,它会在这里创建。
再来演示一个,比方说咱们在文档这个文件夹下打开终端,然后node,我还是去运行那个 12 js 文件。../,先回上一级,再去写代码儿,再来一个 12 table(自动补全Tab),我先不运行,你可以先想一想这个时候如果说我们去新建文件,它会把文件创建在哪个文件夹下?
它会把文件创建在文档这个文件夹下来看,大家可以看到这个 index.HTIML 就创建出来了。
所以说这个相对路径它还是有一点不太稳定的,它会随着你的命令行的工作目录切换而切换。顺便说一下,有些同学他喜欢用这个右键,用插件去运行文件,
其实也会产生一样的问题,有同学可能会这样去操作右键,让 run code 去新建文件,那么这个时候也是达不到自个儿的预期的(并没有在代码文件夹中生成index.html)。
为什么呢?还是因为工作目录所导致的一个问题。好,那刚才我们说了相对路径它不太稳定,不太靠谱,那有没有办法能够解决这个问题?
有,那就是换绝对路径,那么绝对路径每一次手动去写又太麻烦,是吧? d 盘,然后一层一层往里边写,是非常非常繁琐的。这里我们介绍一个新朋友,叫做 __dirname。首先咱们先说一下这个__dirname,大家可以把它理解为是一个全局变量。
全局变量,当然你可以这样理解,我在外层加了引号,为什么加引号?因为它并不是一个真正意义上的全局变量,但是大家这样去理解是没有问题的。好,然后顺便来说一下这个__dirname 它有什么作用?我们来说一下这个变量,它始终保存的是,躲在文件的所在目录的一个绝对路径,这句话有点长,我们慢慢再来说一说。
首先这个__dirname,它保存是这个 dirname,它的所在文件(12-相对路径的Bug.js),
这个文件的所在目录(代码文件夹)
的一个绝对路径,
我们可以尝试去运行一下,看看效果怎么样。
好,右键再次终端打开,然后 node 12 table 敲回车可以看一看,这是咱现在的一个输出结果。
那么现在我切换工作目录,然后再来 node./代码 12 table 再去运行,你注意观察它还是保存的代码的绝对路径。
那么再比如说我右键通过插件去运行 run code,来看一下它保存的,还是代码的绝对路径,
所以说这个变量它保存值是没有发生变化的,我们可以根据这个路径去拼接,最终那个路径形成一个绝对路径。
好,把代码复制一份拿过来,前面写一个__dirname,注意这路径后边没有斜线,我们需要把后边这个点去掉,就可以完成拼接。
那我们来看看效果怎么样。右键终端打开,先来运行 node 12 table 敲回车
看在这儿创建的,我先把它删掉,
然后我 cd..,再去运行 node ./代码,然后 12 table 敲回车,大家看一下儿也可以。
而且我们右键取 run code 看一下,它也可以创建,这就是__dirname 的一个效果,它不会随着你的工作目录的变化而变化,所以我们以后在进行 fs 文件操作时,都会加上这个__dirname,避免因为工作目录的变化而导致程序运行发生变化。
大家同学可能有一个困惑,说不太对劲,你看这个路径,它的分隔符是反斜线,而咱这个路径,它这个分隔符是一个正斜线,这个不会有问题吗?
我说一下,这个代码运行是没有问题的,但是这个写法其实是不太规范的,不过你也不用担心,等后边我们会介绍一个方法,有了它之后我们就可以让路径变得更规范一些。
我们来做一个 fs 的练习,打算对文件做一个批量的重命名。
之前我们写过 12 个与 fs 相关的文件,我们接下来的话打算对 1- 9 这几个文件名字做一个更改,
在它的前面补一个0,就是由原来的1变成01。
为什么做这样一个事情?主要是因为我们这个代码上传到网盘之后,它的显示顺序会发生一些问题,它在排序时是这么排的,一在第一位, 10 在第二位, 11 是第三位,为什么会这样子?因为它是一位一位的比较看,一都是一,它都在前边,这个 2 比这个 10 第一位要大,所以说它就排后边了。
不过我们可以在这个数字前面补一个 0 来解决这个问题,补完 0 之后就会变成这个样子,都是一位比较,
那 0 在前边儿,一位数的文件他们就会排到前边儿了。
好,那么下边儿的话,我们把这个代码儿复制一份儿,
然后把它放到这个练习的文件夹下,放到 code 里边,
咱做一个备份,防止效果出不来,把文件还改岔劈了。
好了,接下来的话,我们打开这个 1-重命名.js 文件,然后第一步我们先导入 fs 模块儿,这个是固定的,跑不了。
导入完毕之后,下边第二步我们打算去读取code这个文件夹当中的文件列表,我们就不挨个一位一位去做了,直接读取好。const files = fsreaddirSync,我们直接采用同步的 API 去做,因为这样的话能省点劲儿。
然后写一个./code,这里的话相对路径还是会存在那个bug,不过我们这里为了方便,待会儿就会在练习的文件夹下去运行这个 js 文件了。
好,首先的话我们先看一看 files 它这个变量能不能获得文件列表,然后我们在命令行里边运行一下子 node 1 杠table,敲回车,大家可以看一下,我们是可以读取到这个文件夹当中的文件列表的,不过读取出来之后这个结果跟在网盘里边的显示结果是一样的。
没事儿能读取到就行。读取到之后我们就要挨个儿去判断,然后挨个儿去处理,所以我们要遍历这个数组,来个 files.foreach,写一个回调函数,我们先看看 item 能不能得到数组当中的每个元素。摁上敲回车。
好,大家可以看一下,是没有问题的,那么没有问题,怎么在这个一位数的数字前面补这个 0 呢?我们要去做一个判断,嘿嘿,我们只要判断前面这个第一位它到底是不是小于 10 就可以了,小于 10 就是一位数,大于等于 10 就是两位数。
好,那怎么来判断?首先我们要先去对它做一个拆分这个文件名,然后 let data 等于一个 item.split,来一个中横线,我们来看看 data 拆分之后的结果怎么样?好,然后摁上敲回车。好,大家可以看一下,我们就拿到了拆分之后的结果,拿到之后我们接下来就可以去判断了。
为了方便,我们声明两个变量,写一个结构赋值,第一个是null,第二个的话咱们来个 name 等于一个data。好,我们先来看看这个 null和 name 能不能获得数字和名字。好,打印一下子 null 和name,然后按上敲回车。大家可以看一下, num 是可以获得这个数字的,而 name 是可以获得这个文件名的。
好,接下来我们开始做判断。如果说 num 要是小于10,表明它是一个一位数,这个 num 它是个字符串,我们可以使用 Number 进行强制类型转换,把它变成一个数字。如果说它要是小于10,说明它是一个一位数,我们需要在它前边一个0,所以 num 的话就应该等于0,加上这个num。
OK,那这个 num 我们的数字变完之后,我们可以去创建新的文件名儿, let newname,就等于num,然后加上这个中横线,再加上这个 name 就可以了。好,我们来看看啊,它的新名字长什么样子,然后摁上敲回车,大家可以看一下 1- 9 这些数字,它们的名字就发生了变化,前边多了一个0。
好,那么新名字有了,旧名字咱们也有,旧名字是谁啊?就是这个item,我们就可以进行重命名了,来重命名, f s.renameSync 括弧,第一个参数是旧的路径./code,然后再加上那个旧的名字。这里的话咱们用一下这个模板字符串儿,这样的话拼接起来更方便一些。
dollar 括弧,把那个名字旧名字拿过来,旧名字是item,OK,拿过来,然后新名字咱写成./code/ $ 括弧,把这个字符串儿变成模板字符串儿。好,然后接下来咱们换上新名字 newName,保存好代码,咱们就已经完成了,下边去看看效果怎么样。我们把 code 文件夹展开,好,见证奇迹的时刻就来了, node 1 杠 TABLE 敲回车。
好,大家可以看一下,这个文件名就改过来了,点开,咱们去看看这个内容有没有发生错位。点开追加的 appendFle 流失,写入的 createwritestream,然后重命名的rename,然后查看资源的stat,没有问题。好,那么到这儿的话,我们整个的效果就是已经达成了,没有什么问题。
最后再留一个思考题,大家下来之后自个可以尝试一下,比如说我把 02 给你删了,就010304,我们的需求是你能不能把这个 03 变 0204 变03,因此类推,让他再按照这个正确的顺序去重命名。那么下来之后大家可以自个试一试,我会把这个答案放在我们的资料文件夹当中。
来学习一下 nodeJS 当中的 path 模块儿,这个模块儿主要是用来操作路径的,这里列了几个API,我们挨个儿给大家演示一下子,为了方便待会儿用,所以我可以先贴一个图。
好,贴完图之后我们切过来。首先的话,我们先来看第一个 path.resolve,这个方法的话,用来拼接规范的绝对路径。
在此之前我们演示过这样一个操作,就使用 __dirname 加上一个路径来拼成一个绝对路径。当时说这种写法它其实并不是特别规范,
那么下边的话,我们再看看这个路径它的一个模样,我们通过 log 来把这个路径打印一下子,然后摁上敲回车。大家可以看一下这个结果,这个结果它的路径分割幅是不统一的,前边用的是反斜线,后边用的是正斜线,而借助于 resolve 我们就可以拼接出比较规范的绝对路径。
好,那么下边我来给大家演示一下子resolve。首先用这个 resolve 之前,我们要先把 path 这个模块儿要先导进来,然后就可以通过 pass.resolve 去调这个方法了。 resolve 这个单词的本意其实是解决问题的意思,而在这儿它接收几个路径的参数。
我们在用这个 resolve 的时候,绝大多数情况第一个参数都会传这个 __dirname 就是传一个绝对路径,然后这后边去传入相对路径,比方说我们传一个点./index.html,这个 resolve 就会和相对路径做一个计算,最后拼接出最终的绝对路径。
我们可以拿使用 console.log 的方式来打印拼接后的结果,然后按上敲回车。大家可以看一下,这就是 resolve 拼接后的绝对路径,它的每一个路径分割符都是反斜线,是一个规范的绝对路径,
然后这里我顺便说一下子,我们在写后续的相对路径时,可以不用去写点杠。我说过这个不写点杠它也是一个相对路径,也可以跟前面这个绝对路径做一个拼接,按上敲回车,大家可以看一下这个结果跟上面这个结果是一样的。
当然有同学说,我在写的时候,我能不能直接去写一个斜线开头的路径,就是第二个参数这块我能不能直接来一个斜线开头参数?我说一下这样做是不对的,如果你这样做的话,第二个参数就不再是一个相对路径了,它成了一个绝对路径了。
如果是绝对路径的话,就会从这个路径(第二个参数)开始跟后边的相对路径(第三、四……参数)去做一个拼接,我们可以看看它的一个返回结果是什么,然后按上敲回车,大家可以看一下直接是 d 盘下边的 index.html,
如果后续我还有别的相对路径,那么他就可以用这个路径(第二个参数)跟后边的相对路径去做一个拼接,产出绝对路径。
所以我们在用这个 resolve 的时候,第一个参数咱们给绝对路径,后边参数都会给相对路径。
顺便说一下,这个 resolve 我们用的稍微多一些,而后边这几个API的话我们用的都比较少,所以给大家演示一下,大家了解就可以了。
我们下边来看看这个sep 的话,它是 separator 单词的一个缩写,意为分隔符,
那么这个属性的话,它是用来保存当前操作系统的路径分隔符,
给大家演示一下 path.sep 保存,然后 摁上交回车。大家可以看一下这里输出是一个反斜线,
不同的操作系统它的路径分隔符是不一样的。在 Windows 下边,路径分隔符是一个反斜线,而在 Linux 系统下边,它的路径分隔符是一个正斜线。
下边的话,咱们再来看看第二个parse,这个是一个方法,用来解析路径,获取路径的相关信息。比方说这里我们先获得一个路径,为了方便,给大家介绍一个新朋友,就是__filename。这个杠杠 filename 有点像我们之前接触过的__dirname,它也是一个全局变量,当然了,它也是加引号的,因为它并不是一个真正意义上的全局变量,但是它可以这样去理解它。
再看看 few name,它所保存的是什么?它所保存的是文件的绝对路径。注意,它所保存的是文件的绝对路径。我们可以来看看这个变量,它的一个结果,按上敲回车,那么此时你会发现我们就获得了这个文件的绝对路径。
OK,有了绝对路径之后,我们可以把这个绝对路径保存在一个变量当中(复制粘贴),这个时候发现颜色不对了,为什么呢?
因为这是一个转移字符,要想正常的去输出反斜线,我们还需要在前面对其做一个转移,使其变为一个普通的反斜线。
好,那么接下来的话,我们就可以通过这个 parse 方法来对这个路径做一个解析了,我们使用 console log 直接去输出,然后将这个路径传进来,看看结果,按上坐回车。大家可以看一下这个方法的返回结果,它是一个对象,里边包含了一些信息,其中 root 是所在的盘符, dir 是文件夹的路径部分,这个 base 是文件名儿,然后 ext 是文件的扩展名儿,还有 name 是这个文件的文件名儿。
好,这是关于 pass 方法它的一个返回结果,然后下边是一些获取的快捷方式,我们挨个儿来看一看。
首先的话是这个 basename,它是用来获取文件名儿的。
好,我们直接打印一下子,然后 path.basename,然后将这个路径传进来,然后摁上敲回车,大家可以看一下快速获取文件名。
那其次还有一个是 dirname,这个是为了方便获取文件夹的路径部分,也就是为了方便获取这一段内容。好,我们来打印一下子path.dirname 括弧,把这个路径复制过来,然后摁上敲回车。好,可以看一下路径部分就有了。
然后其次还有一个是这个 extname,这个是方便获取文件的。扩展好,我们来看看效果 log,然后path.extname,然后摁上敲回车,OK,文件扩展名获取到了。