NIO学习--核心概念与基本读写
这两天花了时间学习了java的nio,看的书是Ron Hitchens著的 《Java NIO》,总的来说,这本书真的写的非常好,而且整本书将java nio的内容从底层讲了个遍,书不厚,但是确实值得一读,这里总结一下学习后的一些心得。学习过程中既详细看完了《Java NIO》这本书,同时也参照了http://zhangshixi.iteye.com/blog/679959该作者写的关于NIO的博文。好了,我们正式开始NIO的学习吧。
首先,简单说说I/O和NIO一些概念性的东西吧。
I/O流或者输入/输出流指的是计算机与外部世界或者一个程序与计算机的其余部分的之间的接口。新的输入/输出(NIO)库是在JDK 1.4中引入的。NIO弥补了原来的I/O的不足,它在标准Java代码中提供了高速的、面向块的I/O。原来的I/O库与NIO最重要的区别是数据打包和传输的方式的不同,原来的 I/O 以流 的方式处理数据,而 NIO 以块 的方式处理数据。 面向流的I/O系统一次一个字节地处理数据。一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。为流式数据创建过滤器非常容易。链接几个过滤 器,以便每个过滤器只负责单个复杂处理机制的一部分,这样也是相对简单的。不利的一面是,面向流的I/O通常相当慢。
NIO与原来的I/O有同样的作用和目的,但是它使用块I/O的处理方式。每一个操作都在一步中产生或者消费一个数据块。按块处理数据比按(流式的)字节处理数据要快得多。但是面向块的I/O缺少一些面向流的I/O所具有的优雅性和简单性。
我们首先从一个例子开始NIO的学习
我们分别用I/O以及NIO来实现从一个文件中读取内容并将其打印出来,看看I/O和NIO的特点。
使用IO来读取指定文件中的前1024字节并打印出来:
/** * 使用IO读取指定文件的前1024个字节的内容。 * @param args * @throws Exception */ public static void main(String[] args) throws Exception { FileInputStream is = new FileInputStream("GitHub.txt"); byte[] buffer = new byte[1024]; is.read(buffer); System.out.println(new String(buffer)); is.close(); } /** * 使用NIO读取指定文件的前1024个字节的内容。 * @param args * @throws Exception */ public static void main(String[] args) throws Exception { FileInputStream is = new FileInputStream("GitHub.txt"); //为该文件输入流生成唯一的文件通道 FileChannel FileChannel channel = is.getChannel(); //开辟一个长度为1024的字节缓冲区 ByteBuffer buffer = ByteBuffer.allocate(1024); channel.read(buffer); System.out.println(new String(buffer.array())); System.out.println(buffer.isDirect() + ", " + buffer.isReadOnly()); channel.close(); is.close(); }
从上面的例子中可以看出,NIO以通道Channel和缓冲区Buffer为基础来实现面向块的IO数据处理。下面将讨论并学习NIO 库的核心概念以及从高级的特性到底层编程细节的几乎所有方面。
NIO中的读和写
1) 概述:
读和写是I/O的基本过程。从一个通道中读取很简单:只需创建一个缓冲区,然后让通道将数据读到这个缓冲区中。写入也相当简单:创建一个缓冲区,用数据填充它,然后让通 道用这些数据来执行写入操作。
2) 从文件中读取:
如果使用原来的I/O,那么我们只需创建一个FileInputStream并从它那里读取。而在NIO中,情况稍有不同:我们首先从FileInputStream获取一个FileChannel对象,然后使用这个通道来读取数据。
在NIO系统中,任何时候执行一个读操作,您都是从通道中读取,但是您不是直接从通道读取。因为所有数据最终都驻留在缓冲区中,所以您是从通道读到缓冲区中。
因此读取文件涉及三个步骤:
(1) 从FileInputStream获取Channel。
(2) 创建Buffer。
(3) 将数据从Channel读到Buffer 中。
现在,让我们看一下这个过程。
// 第一步是获取通道。我们从 FileInputStream 获取通道: FileInputStream fin = new FileInputStream( "readandshow.txt" ); FileChannel fc = fin.getChannel(); // 下一步是创建缓冲区: ByteBuffer buffer = ByteBuffer.allocate( 1024 ); // 最后,需要将数据从通道读到缓冲区中: fc.read( buffer );
您会注意到,我们不需要告诉通道要读多少数据到缓冲区中。每一个缓冲区都有复杂的内部统计机制,它会跟踪已经读了多少数据以及还有多少空间可以容纳更多的数据。我们将在缓冲区内部细节中介绍更多关于缓冲区统计机制的内容。
3) 写入文件:
在 NIO 中写入文件类似于从文件中读取。
// 首先从 FileOutputStream 获取一个通道: FileOutputStream fout = new FileOutputStream( "writesomebytes.txt" ); FileChannel fc = fout.getChannel(); // 下一步是创建一个缓冲区并在其中放入一些数据,这里,用message来表示一个持有数据的数组。 ByteBuffer buffer = ByteBuffer.allocate( 1024 ); for (int i=0; i<message.length; ++i) { buffer.put( message[i] ); } buffer.flip(); // 最后一步是写入缓冲区中: fc.write( buffer );
注意在这里同样不需要告诉通道要写入多数据。缓冲区的内部统计机制会跟踪它包含多少数据以及还有多少数据要写入。
4) 读写结合:
最后将展示使用读写结合,将一个文件的所有内容拷贝到另一个文件中。
/** * 将一个文件的所有内容拷贝到另一个文件中。 * * CopyFile.java 执行三个基本操作: * 首先创建一个 Buffer,然后从源文件中将数据读到这个缓冲区中,然后将缓冲区写入目标文件。 * 程序不断重复 — 读、写、读、写 — 直到源文件结束。 * */ public class CopyFile { public static void main(String[] args) throws Exception { String infile = "GitHub.txt"; String outfile = "GitHub2.txt"; // 获取源文件和目标文件的输入输出流 FileInputStream fin = new FileInputStream(infile); FileOutputStream fout = new FileOutputStream(outfile); // 获取输入输出通道 FileChannel fcin = fin.getChannel(); FileChannel fcout = fout.getChannel(); // 创建缓冲区 ByteBuffer buffer = ByteBuffer.allocate(1024); while (true) { // clear方法重设缓冲区,使它可以接受读入的数据 buffer.clear(); // 从输入通道中将数据读到缓冲区 int r = fcin.read(buffer); // read方法返回读取的字节数,可能为零,如果该通道已到达流的末尾,则返回-1 if (r == -1) { break; } // flip方法让缓冲区可以将新读入的数据写入另一个通道 buffer.flip(); // 从输出通道中将数据写入缓冲区 fcout.write(buffer); } } }
下一篇我们将会深度讲解一下NIO中的缓冲区Buffer。