本文讨论Java的NIO框架中,缓冲区与通道(Channel)的概念
缓冲区(Buffer)
下面的代码会在桌面上生成一个叫A.txt
的文件,并且文件里面有两行我爱武汉汉阳的妹子
,这说明wirte()
方法可以直接操作文件
public static void main(String[] args) throws Exception {
FileOutputStream f = new FileOutputStream("C:\\Users\\admin\\Desktop\\A.txt");
for (int i = 0; i < 2; i++) {
f.write("我爱武汉汉阳的妹子\n".getBytes(StandardCharsets.UTF_8));
}
}
我们将上面的代码装饰一下(java.io框架是经典的装饰者模式),变成下面这个样子,则A.txt
文件中不会有任何内容(注意运行前先删除A.txt里的内容),BufferedOutputStream的具体方式可以查看这里
FileOutputStream f = new FileOutputStream("C:\\Users\\admin\\Desktop\\A.txt");
BufferedOutputStream buffer = new BufferedOutputStream(f);
for (int i = 0; i < 2; i++) {
buffer.write("我爱武汉汉阳的妹子\n".getBytes(StandardCharsets.UTF_8));
}
我们将上面的代码增加一行buffer.flush();
,变成下面这个样子,则A.txt
文件中的内容会变得正确
FileOutputStream f = new FileOutputStream("C:\\Users\\admin\\Desktop\\A.txt");
BufferedOutputStream buffer = new BufferedOutputStream(f);
for (int i = 0; i < 2; i++) {
buffer.write("我爱武汉汉阳的妹子\n".getBytes(StandardCharsets.UTF_8));
}
buffer.flush();// 多了一行这个
综上所述,本文截止到这里,可以得出下面这个结论:flush()
是给Buffer使用的,如果一个类不具有缓冲区功能,那么它的flush()
方法是没有任何用的
那么什么是buffer呢?
从Java的I/O框架中的类结构来说,Buffer是个抽象类,定义在java.nio包,其中ByteBuffer类是它最常用的实现,而在上述的示例中,BufferedOutputStream并没有继承Buffer类,但它依然是一个Buffer,虽然没有继承,但这并不影响 flush()
是给Buffer使用的 这个结论
从内存管理的角度来说,Buffer是一块内存区域,当这块内存区域满了或者调用flush方法的时候,则会将这块内存中的所有数据一次性写入到硬盘
到此位置,我们应该知道了FileOutputStream与BufferedOutputStream的区别,区别就是FileOutputStream在调用write的时候,是直接将数据从堆中,写到硬盘上的,而BufferedOutputStream调用write的时候,是将数据写到缓冲区
中暂存起来,也正是因为这个区别,下面的代码m1的性能要远远远远高于m2
public static void main(String[] args) throws Exception {
m1();
m2();
}
public static void m1() throws Exception {
long t1 = System.currentTimeMillis();
FileOutputStream f = new FileOutputStream("C:\\Users\\admin\\Desktop\\A.txt");
BufferedOutputStream buffer = new BufferedOutputStream(f);
for (int i = 0; i < 10000000; i++) {
buffer.write("我爱武汉汉阳的妹子".getBytes(StandardCharsets.UTF_8));
}
buffer.flush();
buffer.close();
long t2 = System.currentTimeMillis();
System.out.println("m1:" + (t2 - t1));
}
public static void m2() throws Exception {
long t1 = System.currentTimeMillis();
FileOutputStream f = new FileOutputStream("C:\\Users\\admin\\Desktop\\B.txt");
for (int i = 0; i < 10000000; i++) {
f.write("我爱武汉汉阳的妹子".getBytes(StandardCharsets.UTF_8));
}
long t2 = System.currentTimeMillis();
System.out.println("m2:" + (t2 - t1));
}
结论: Buffer利用一块内存区域将数据暂存起来,调用flush的时候(或者buffer满了的时候),将这些数据一次性写入硬盘,如果不用Buffer,则需要循环和硬盘交互(分批写入),这个速度是非常慢的,主要就是性能上的差异,至于说Buffer能避免OOM,则占比不是很大,所以需要注意flush方法是和Buffer息息相关的,如果这个类不是Buffer,则flush方法是一个空方法,这点从OutputStream
的源码中就能看出来
通道(Channel)
从类结构上来说Channel是个接口,定义在java.nio.channels
从内存管理的角度来说
1.当从硬盘上的一个文件中读数据的时候,channel先从文件中读取数据,然后存到存到buffer,我们写的代码再从buffer中拿数据
2.当往一个文件中写数据的时候,我们自己写的代码先往buffer中写数据,然后buffer再写入channel,最后由channel写到硬盘上
我们再来说Buffer,本文截至到现在,可知Buffer占据了一片内存区域,这片内存区域是存在于堆中的,这种Buffer叫做non-direct buffers,还有一种Buffer区域,叫做direct buffers(直接缓冲区),它不在堆中,是在堆外面一块独有的内存区域,下面的代码在堆的外面创建了一个1024字节大小的direct buffers(直接缓冲区)
,然后将这个缓冲区里的内容写到文件上
public static void main(String[] args) throws Exception {
byte[] msg = "我爱武汉汉阳的妹子".getBytes(StandardCharsets.UTF_8);
FileOutputStream out = new FileOutputStream("C:\\Users\\admin\\Desktop\\C.txt");
// 其中ByteBuffer是NIO中非常重要的类
ByteBuffer buffer = ByteBuffer.allocateDirect(msg.length);
buffer.put(msg);
buffer.flip();
FileChannel channel = out.getChannel();
channel.write(buffer);
channel.close();
buffer.clear();
}
本文就到这里结束了,主要想说缓冲区与通道的关系,文章开头只是为了强调一下flush是buffer才有的东西,不是buffer是没有flush的,后文说道通道,主要是为了讲述NIO中的概念,网络编程的时候这个概念是比较重要的