文档章节

NIO学习系列:核心概念及基本读写

LittlerBeans
 LittlerBeans
发布于 2015/02/15 11:02
字数 2466
阅读 822
收藏 22

1.    引言 
   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所具有的优雅性和简单性。

 

2.    从一个例子开始 
   下面我们从一个简单的使用IO和NIO读取一个文件中的内容为例,来进入NIO的学习之旅。
   使用IO来读取指定文件中的前1024字节并打印出来:

Java代码  收藏代码

  1. /** 

  2.  * 使用IO读取指定文件的前1024个字节的内容。 

  3.  * @param file 指定文件名称。 

  4.  * @throws java.io.IOException IO异常。 

  5.  */  

  6. public void ioRead(String file) throws IOException {  

  7.     FileInputStream in = new FileInputStream(file);  

  8.     byte[] b = new byte[1024];  

  9.     in.read(b);  

  10.     System.out.println(new String(b));  

  11. }  

  12.   

  13. /** 

  14.  * 使用NIO读取指定文件的前1024个字节的内容。 

  15.  * @param file 指定文件名称。 

  16.  * @throws java.io.IOException IO异常。 

  17.  */  

  18. public void nioRead(String file) throws IOException {  

  19.     FileInputStream in = new FileInputStream(file);  

  20.     FileChannel channel = in.getChannel();  

  21.   

  22.     ByteBuffer buffer = ByteBuffer.allocate(1024);  

  23.     channel.read(buffer);  

  24.     byte[] b = buffer.array();  

  25.     System.out.println(new String(b));  

  26. }  

   从上面的例子中可以看出,NIO以通道Channel和缓冲区Buffer为基础来实现面向块的IO数据处理。下面将讨论并学习NIO 库的核心概念以及从高级的特性到底层编程细节的几乎所有方面。


3.    核心概念:通道和缓冲区 
   1)    概述: 
   通道和缓冲区是NIO中的核心对象,几乎在每一个I/O操作中都要使用它们。 
   通道Channel是对原I/O包中的流的模拟。到任何目的地(或来自任何地方)的所有数据都必须通过一个Channel对象。
   缓冲区Buffer实质上是一个容器对象。发送给一个通道的所有对象都必须首先放到缓冲区中;同样地,从通道中读取的任何数据都要读到缓冲区中。 

   2)    缓冲区: 
   Buffer是一个容器对象,它包含一些要写入或者刚读出的数据。在NIO中加入Buffer对象,体现了新库与原I/O的一个重要区别。在面向流的I/O中,您将数据直接写入或者将数据直接读到Stream对象中。 
   在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的。在写入数据时,它是写入到缓冲区中的。任何时候访问NIO中的数据,您都是将它放到缓冲区中。 
   缓冲区实质上是一个数组。通常它是一个字节数组,但是也可以使用其他种类的数组。但是一个缓冲区不仅仅是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。 
   最常用的缓冲区类型是ByteBuffer。 一个ByteBuffer可以在其底层字节数组上进行get/set操作(即字节的获取和设置)。 
   ByteBuffer不是NIO中唯一的缓冲区类型。事实上,对于每一种基本Java类型都有一种缓冲区类型: 
   ByteBuffer 
   CharBuffer 
   ShortBuffer 
   IntBuffer 
   LongBuffer 
   FloatBuffer 
   DoubleBuffer 
   每一个Buffer类都是Buffer接口的一个实例。 除了ByteBuffer, 每一个Buffer类都有完全一样的操作,只是它们所处理的数据类型不一样。因为大多数标准I/O操作都使用ByteBuffer,所以它具有所有共享的缓冲区操作以及一些特有的操作。 
   下面的UseFloatBuffer列举了使用类型化的缓冲区FloatBuffer的一个应用例子:

Java代码  收藏代码

  1. /** 

  2.  * 使用 float 缓冲区。 

  3.  * @version 1.00 2010-5-19, 10:30:59 

  4.  * @since 1.5 

  5.  * @author ZhangShixi 

  6.  */  

  7. public class UseFloatBuffer {  

  8.   

  9.     public static void main(String[] args) {  

  10.         // 分配一个容量为10的新的 float 缓冲区  

  11.         FloatBuffer buffer = FloatBuffer.allocate(10);  

  12.         for (int i = 0; i < buffer.capacity(); i++) {  

  13.             float f = (float) Math.sin((((float) i) / 10) * (2 * Math.PI));  

  14.             buffer.put(f);  

  15.         }  

  16.         // 反转此缓冲区  

  17.         buffer.flip();  

  18.   

  19.         // 告知在当前位置和限制之间是否有元素  

  20.         while (buffer.hasRemaining()) {  

  21.             float f = buffer.get();  

  22.             System.out.println(f);  

  23.         }  

  24.     }  

  25. }  

 

   3)    通道: 
   Channel是对原I/O包中的流的模拟,可以通过它读取和写入数据。拿NIO与原来的I/O做个比较,通道就像是流。 
   正如前面提到的,所有数据都通过Buffer对象来处理。您永远不会将字节直接写入通道中,相反,您是将数据写入包含一个或者多个字节的缓冲区。同样,您不会直接从通道中读取字节,而是将数据从通道 读入缓冲区,再从缓冲区获取这个字节。 
通道与流的不同之处在于通道是双向的。而流只是在一个方向上移动(一个流必须是InputStream或者OutputStream的子类), 而通道可以用于读、写或者同时用于读写。 
   因为它们是双向的,所以通道可以比流更好地反映底层操作系统的真实情况。特别是在UNIX模型中,底层操作系统通道是双向的。


4.    从理论到实践:NIO中的读和写 
   1)    概述: 
   读和写是I/O的基本过程。从一个通道中读取很简单:只需创建一个缓冲区,然后让通道将数据读到这个缓冲区中。写入也相当简单:创建一个缓冲区,用数据填充它,然后让通 道用这些数据来执行写入操作。 

   2)    从文件中读取: 
如果使用原来的I/O,那么我们只需创建一个FileInputStream并从它那里读取。而在NIO中,情况稍有不同:我们首先从FileInputStream获取一个FileChannel对象,然后使用这个通道来读取数据。 
   在NIO系统中,任何时候执行一个读操作,您都是从通道中读取,但是您不是直接从通道读取。因为所有数据最终都驻留在缓冲区中,所以您是从通道读到缓冲区中。 
   因此读取文件涉及三个步骤:
   (1) 从FileInputStream获取Channel。
   (2) 创建Buffer。
   (3) 将数据从Channel读到Buffer 中。 
   现在,让我们看一下这个过程。

Java代码  收藏代码

  1. // 第一步是获取通道。我们从 FileInputStream 获取通道:   

  2. FileInputStream fin = new FileInputStream( "readandshow.txt" );  

  3. FileChannel fc = fin.getChannel();  

  4. // 下一步是创建缓冲区:   

  5. ByteBuffer buffer = ByteBuffer.allocate( 1024 );  

  6. // 最后,需要将数据从通道读到缓冲区中:   

  7. fc.read( buffer );  

   您会注意到,我们不需要告诉通道要读多少数据到缓冲区中。每一个缓冲区都有复杂的内部统计机制,它会跟踪已经读了多少数据以及还有多少空间可以容纳更多的数据。我们将在缓冲区内部细节中介绍更多关于缓冲区统计机制的内容。 

   3)    写入文件: 
   在 NIO 中写入文件类似于从文件中读取。

Java代码  收藏代码

  1. // 首先从 FileOutputStream 获取一个通道:   

  2. FileOutputStream fout = new FileOutputStream( "writesomebytes.txt" );  

  3. FileChannel fc = fout.getChannel();  

  4. // 下一步是创建一个缓冲区并在其中放入一些数据,这里,用message来表示一个持有数据的数组。   

  5. ByteBuffer buffer = ByteBuffer.allocate( 1024 );  

  6. for (int i=0; i<message.length; ++i) {  

  7.      buffer.put( message[i] );  

  8. }  

  9. buffer.flip();  

  10. // 最后一步是写入缓冲区中:   

  11. fc.write( buffer );  

    注意在这里同样不需要告诉通道要写入多数据。缓冲区的内部统计机制会跟踪它包含多少数据以及还有多少数据要写入。 

   4)    读写结合: 
   下面的示例将展示使用读写结合,将一个文件的所有内容拷贝到另一个文件中。

Java代码  收藏代码

  1. /** 

  2.  * 将一个文件的所有内容拷贝到另一个文件中。 

  3.  *  

  4.  * CopyFile.java 执行三个基本操作: 

  5.  * 首先创建一个 Buffer,然后从源文件中将数据读到这个缓冲区中,然后将缓冲区写入目标文件。 

  6.  * 程序不断重复 — 读、写、读、写 — 直到源文件结束。 

  7.  *  

  8.  * @version 1.00 2010-5-19, 10:49:46 

  9.  * @since 1.5 

  10.  * @author ZhangShixi 

  11.  */  

  12. public class CopyFile {  

  13.   

  14.     public static void main(String[] args) throws Exception {  

  15.         String infile = "C:\\copy.sql";  

  16.         String outfile = "C:\\copy.txt";  

  17.   

  18.         // 获取源文件和目标文件的输入输出流  

  19.         FileInputStream fin = new FileInputStream(infile);  

  20.         FileOutputStream fout = new FileOutputStream(outfile);  

  21.   

  22.         // 获取输入输出通道  

  23.         FileChannel fcin = fin.getChannel();  

  24.         FileChannel fcout = fout.getChannel();  

  25.   

  26.         // 创建缓冲区  

  27.         ByteBuffer buffer = ByteBuffer.allocate(1024);  

  28.   

  29.         while (true) {  

  30.             // clear方法重设缓冲区,使它可以接受读入的数据  

  31.             buffer.clear();  

  32.   

  33.             // 从输入通道中将数据读到缓冲区  

  34.             int r = fcin.read(buffer);  

  35.   

  36.             // read方法返回读取的字节数,可能为零,如果该通道已到达流的末尾,则返回-1  

  37.             if (r == -1) {  

  38.                 break;  

  39.             }  

  40.   

  41.             // flip方法让缓冲区可以将新读入的数据写入另一个通道  

  42.             buffer.flip();  

  43.   

  44.             // 从输出通道中将数据写入缓冲区  

  45.             fcout.write(buffer);  

  46.         }  

  47.     }  

  48. }  


本文转载自:http://zhangshixi.iteye.com/blog/679959

共有 人打赏支持
LittlerBeans
粉丝 10
博文 26
码字总数 14592
作品 0
杭州
高级程序员
私信 提问
加载中

评论(1)

车开源
车开源
学习了,比如在web中有个动态下载文件的servlet,采用NIO是否更优呢?(分两种情况:小文件10兆以下和大文件10兆以上甚至更大)
架构师必备技能之Netty 高并发 UTS 项目实战

一、Netty Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。 也就是说,Netty 是一个基...

A尚学堂Nancy老师
08/27
0
0
JAVA NIO编程入门(一)

JAVA NIO编程入门(一) 一、前言 笔者之前接触的NIO编程比较少,所以对这一块的基础也比较弱,NIO作为java编程中一个重要的模块,不能很好的掌握它,感觉自己在java方面就掌握的不够,所以,...

木木匠
09/01
0
0
Java NIO核心概念及基本读写

面向流的I/O系统一次一个字节地处理数据。一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。为流式数据创建过滤器非常容易。链接几个过滤器,以便每个过滤器只负责单个复杂处理...

BravoZu
2014/02/19
273
1
Netty那点事(二)Netty中的buffer

上一篇文章我们概要介绍了Netty的原理及结构,下面几篇文章我们开始对Netty的各个模块进行比较详细的分析。Netty的结构最底层是buffer机制,这部分也相对独立,我们就先从buffer讲起。 What...

黄亿华
2013/09/25
0
4
Java IO/NIO学习总结

下面是自己学习整理Java IO/NIO的总结,期间浏览了网上很多优秀的总结分析文章,一并贴在这里供大家学习参考。IO的知识点学习大概分为以下几个部分: 概念理解 熟悉Java IO API 熟悉Java NI...

isam
2016/03/22
208
0

没有更多内容

加载失败,请刷新页面

加载更多

MySQL Replication 梳理详解

MySQL Replication 1 MySQL5.5以前的复制 异步、SQL线程串行化回放 MySQL内建的复制功能是构建大型,高性能应用程序的基础。主服务器将更新写入二进制日志文件,从服务器重新执行一遍来实现的...

PeakFang-BOK
3分钟前
0
0
.NET Core & ConsoleApp & appsettings.json

准备 Visual Studio 2017 .NET Core 2.1 新建控制台应用(.NET Core) 默认的 Program.cs // Program.csusing System;namespace ConsoleApp1{ class Program { static voi......

taadis
13分钟前
0
0
结合lucene谈谈日期的压缩问题

说起日期值的压缩,一般容易想到的办法是将日期转化成long类型,然后再通过变长整形进行压缩,我算了一下按照毫秒来算最多占用5个字节(可以通过“谈谈变长整型”中的表查看),确实节省了部...

FAT_mt
59分钟前
1
0
导出私有函数与私有变量

在Go语言中, package中包含函数与变量通过identifier的首字母是否大写来决定它是否可以被其它package所访问。当一个函数或变量名称为小写字母时,默认是无法被其他package引用的. 有没有办法...

xtof
今天
1
0
new Date() 在Safari下的 Invalid Date问题

问题复现 var timeStr = '2018-11-11 00:00:00';var time = new Date(timeStr);// error: Invalid Date... 在safari浏览器下,time为Invalid Date, 导致后面代码执行错误; 其他浏览器诸...

会写代码的husky
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部