首先,我们来看Node.Js读取文件的操作
var http = require('http');
var fs = require('fs');
var path = require('path');
//文件读取
var server = http.createServer(function (req, res) {
var fileName = path.resolve(__dirname, 'input.txt');
fs.readFile(fileName, function (err, data) {
res.end(data);
});
});
server.listen(8888);
这样就可以读取到同级目录下的input.txt文件了,仔细看代码,也没问题,可是当遇到文件比较大,有一个G,两个G呢?代码就不能这样写了,这个时候,就应该用stream流了:
var http = require('http');
var fs = require('fs');
var path = require('path');
//以流的方式文件读取。
var http = require('http');
var fs = require('fs');
var path = require('path');
var server = http.createServer(function (req, res) {
var fileName = path.resolve(__dirname, 'data.txt');
var stream = fs.createReadStream(fileName); //创建一个读的流
stream.pipe(res); // 将 res 作为 stream 的 接受者
});
server.listen(8888);
input.txt照样可以输出到网页。于是,我们现在详细讲一下这个流:
图片摘自菜鸟教程为什么要用流呢?我们把要输入的文件,比作一个水桶,我们要抱起他,要用很大力气,这里的力气指的是
计算机硬件的性能, CPU 的运算,内存的存储,硬盘和网络的读写速度。但是我们没必要抱起它,弄一个水管就好了,这就是stream的作用,同理,我们看视频,也是同一个道理,不可能全部加载完再让你看。
知道了流的原理,那我们写一个文件拷贝吧:
Node版本
let fileName1 = path.resolve(__dirname,'input.txt');
let fileName2 = path.resolve(__dirname,'input1.txt');
let readStream = fs.createReadStream(fileName1);
let writeStream = fs.createWriteStream(fileName2);
readStream.pipe(writeStream);
readStream.on("end",function(){
console.log("拷贝完成");
})
Java版本,字节流(适合二进制文件,图片,视频,音频 ):
public static void demo() throws IOException {
//1.获取目标路径
//第一种方法:通过字符串
//String srcPath = "图片路径";
//String destPath = "图片路径";
//第二种方法:通过文件类
File srcPath = new File("图片路径");
File destPath = new File("图片路径");
//2.创建通道,依次 打开输入流,输出流
FileInputStream fis = new FileInputStream(srcPath);
FileOutputStream fos = new FileOutputStream(destPath);
byte[] bt = new byte[1024];
//3.读取和写入信息(边读取边写入)
while (fis.read(bt) != -1) { //读取
fos.write(bt); //写入
}
//4.依次 关闭流(先开后关,后开先关)
fos.close();//先关输出流
fis.close();//后关输入流
}
Java版本,字符流:(只能读文本之类的文件,但字符流提供缓冲(Buffer)功能可以提高读取效率。)
public static void demo() throws IOException {
//获取目标路径
File srcPath = new File("图片路径");
File destPath = new File("图片路径");
//创建通道,依次 打开输入流,输出流
FileReader fis = new FileReader(srcPath);
FileWriter fos = new FileWriter(destPath);
char[] ch = new char[1024];
int length = 0;
// 读取和写入信息(边读取边写入)
while ((length = fis.read(ch)) != -1) { //读取
fos.write(ch, 0, length); //写入
fos.flush();
}
// 依次 关闭流(先开后关,后开先关)
fos.close();//先关输出流
fis.close();//后关输入流
}
菜鸟教程IO示意图
IO流就讲这么多了,其实就是数据不停地搬入搬出缓冲区Buffer而已,上面提到了Buffer缓冲区,以及读取二进制文件,拷贝功能,这就不得不扯到一个底层的东西,那就是操作系统。上面的代码,Java工资5k可以写出来,工资5w,也可以写出来,还有可能写的一样,但是Java5k的人,只会写出这段代码,而5w的人,不仅可以写出这段代码,还理解操作系统的底层原理,还可以自己用底层,自己实现一个IO类,这就是区别。下面就是操作系统的讲解啦,如果想要工资1W+,这东西是必须知道的!
操作系统
关于操作系统,最近在准备软件设计师的考试,以及考研,也会考到操作系统的知识,下面有些理论是抄自书上的。
五大IO模型
IO是什么?,I就是从硬盘到内存,O就是从内存到硬盘。关于五大IO模型,我用白话文讲述下,因为官方文档,挺绕的。
(1)阻塞:
我从硬盘读取数据,然后程序一直等,数据读完后,继续操作。这种方式是最简单的,程序多了,自然就不行了。
(2)非阻塞
我从硬盘读取数据,然后程序跑,我去做其他事,等跑完了,再叫我做事。
(3)IO多路复用
多路复用在网络中,也经常用到,比如HTTP2,多路就是一个线程有多个IO,如果还被阻塞,有一个,或多个成功了,就返回调用。需要线程遍历全部IO,判断是哪个IO有数据。例如Java网络里的socket 的 select() 函数,线程调用 select() 进入阻塞态,任何一个IO有数据了,线程就退出阻塞态,获得机会继续执行。
(4)信号驱动IO
给一个IO注册一个信号和信号触发的回调函数,一旦信号被触发,回调函数里读取数据。
例如给 socket 注册一个“可读”的信号,当数据来了,可读的时候,信号被触发,执行回调函数从内核cache复制数据到用户空间。
(5)异步IO
异步IO中,操作系统完成了数据从内核到用户空间的拷贝后,以信号的方式通知用户线程可以下一步操作。省去了用户线程阻塞下来拷贝数据的过程。
用户空间和内核空间:
内核空间:Java的IO操作都是由操作系统来执行的,比如磁盘到内存,内存到磁盘,都是发生在内核空间的。
用户空间:JVM的堆(进程的内存空间)在这里的程序和指令是访问IO是--被限制的,只能通过内核去访问硬件,不能JVM直接访问IO,因为安全性问题。
Linux就是这样子的,关于这个,体系比较庞大,都可以取一个专题了。
原理
(1)用户程序发起读操作,导致“ syscall read ”系统调用,操作系统接收到调用后,会创建一个缓冲区Buffer,用来存放需要读取的数据。
(2)创建后,同时向IO设备(设备控制器)发送读取指令(包括读哪些内容),IO硬件接收到指令后,进行读取,并把数据通过DMA(直接存取器)放入内核空间的缓冲区中(Buffer)。
(3)操作系统将内核缓冲区中的数据拷贝到用户空间的缓存中,用户发起写操作,导致 “syscall write ”系统调用,将会把一个 buffer 中的数据搬出去,写到磁盘文件,小文件为普通IO,大文件为zerocopy技术。(4)Buffer其实可以理解成一个中转站,在java,php,node中都会用到,比如页面静态化。
网友评论