Java NIO 详解(一)
一、基本概念描述
1.1 I/O简介
I/O即输入输出,是计算机与外界世界的一个借口。IO操作的实际主题是操作系统。在java编程中,一般使用流的方式来处理IO,所有的IO都被视作是单个字节的移动,通过stream对象一次移动一个字节。流IO负责把对象转换为字节,然后再转换为对象。
关于Java IO相关知识请参考我的另一篇文章:Java IO 详解
1.2 什么是NIO
NIO即New IO,这个库是在JDK1.4中才引入的。NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多。
在Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO,本篇文章重点介绍标NIO,关于网络编程NIO请见Java NIO详解(二)。
1.3 流与块的比较
NIO和IO最大的区别是数据打包和传输方式。IO是以流的方式处理数据,而NIO是以块的方式处理数据。
面向流的IO一次一个字节的处理数据,一个输入流产生一个字节,一个输出流就消费一个字节。为流式数据创建过滤器就变得非常容易,链接几个过滤器,以便对数据进行处理非常方便而简单,但是面向流的IO通常处理的很慢。
面向块的IO系统以块的形式处理数据。每一个操作都在一步中产生或消费一个数据块。按块要比按流快的多,但面向块的IO缺少了面向流IO所具有的有雅兴和简单性。
二、NIO基础
Buffer和Channel是标准NIO中的核心对象(网络NIO中还有个Selector核心对象,具体请参考Java NIO详解(二)),几乎每一个IO操作中都会用到它们。
Channel是对原IO中流的模拟,任何来源和目的数据都必须通过一个Channel对象。一个Buffer实质上是一个容器对象,发给Channel的所有对象都必须先放到Buffer中;同样的,从Channel中读取的任何数据都要读到Buffer中。
2.1 关于Buffer
Buffer是一个对象,它包含一些要写入或读出的数据。在NIO中,数据是放入buffer对象的,而在IO中,数据是直接写入或者读到Stream对象的。应用程序不能直接对 Channel 进行读写操作,而必须通过 Buffer 来进行,即 Channel 是通过 Buffer 来读写数据的。
在NIO中,所有的数据都是用Buffer处理的,它是NIO读写数据的中转池。Buffer实质上是一个数组,通常是一个字节数据,但也可以是其他类型的数组。但一个缓冲区不仅仅是一个数组,重要的是它提供了对数据的结构化访问,而且还可以跟踪系统的读写进程。
使用 Buffer 读写数据一般遵循以下四个步骤:
- 写入数据到 Buffer;
- 调用 flip() 方法;
- 从 Buffer 中读取数据;
- 调用 clear() 方法或者 compact() 方法。
当向 Buffer 写入数据时,Buffer 会记录下写了多少数据。一旦要读取数据,需要通过 flip() 方法将 Buffer 从写模式切换到读模式。在读模式下,可以读取之前写入到 Buffer 的所有数据。
一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用 clear() 或 compact() 方法。clear() 方法会清空整个缓冲区。compact() 方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。
Buffer主要有如下几种:
2.3 关于Channel
Channel是一个对象,可以通过它读取和写入数据。可以把它看做IO中的流。但是它和流相比还有一些不同:
- Channel是双向的,既可以读又可以写,而流是单向的
- Channel可以进行异步的读写
- 对Channel的读写必须通过buffer对象
正如上面提到的,所有数据都通过Buffer对象处理,所以,您永远不会将字节直接写入到Channel中,相反,您是将数据写入到Buffer中;同样,您也不会从Channel中读取字节,而是将数据从Channel读入Buffer,再从Buffer获取这个字节。
因为Channel是双向的,所以Channel可以比流更好地反映出底层操作系统的真实情况。特别是在Unix模型中,底层操作系统通常都是双向的。
在Java NIO中Channel主要有如下几种类型:
- FileChannel:从文件读取数据的
- DatagramChannel:读写UDP网络协议数据
- SocketChannel:读写TCP网络协议数据
- ServerSocketChannel:可以监听TCP连接
三、从理论到实践:NIO中的读和写
IO中的读和写,对应的是数据和Stream,NIO中的读和写,则对应的就是通道和缓冲区。NIO中从通道中读取:创建一个缓冲区,然后让通道读取数据到缓冲区。NIO写入数据到通道:创建一个缓冲区,用数据填充它,然后让通道用这些数据来执行写入。
3.1 从文件中读取
我们已经知道,在NIO系统中,任何时候执行一个读操作,您都是从Channel中读取,而您不是直接从Channel中读取数据,因为所有的数据都必须用Buffer来封装,所以您应该是从Channel读取数据到Buffer。
因此,如果从文件读取数据的话,需要如下三步:
- 从FileInputStream获取Channel
- 创建Buffer
- 从Channel读取数据到Buffer
下面我们看一下具体过程:
第一步:获取通道
FileInputStream fin = new FileInputStream( "readandshow.txt" );
FileChannel fc = fin.getChannel();
123
第二步:创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
12
第三步:将数据从通道读到缓冲区
fc.read( buffer );
12
3.2 写入数据到文件
类似于从文件读数据,
第一步:获取一个通道
FileOutputStream fout = new FileOutputStream( "writesomebytes.txt" );
FileChannel fc = fout.getChannel();
123
第二步:创建缓冲区,将数据放入缓冲区
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
for (int i=0; i<message.length; ++i) {
buffer.put( message[i] );
}
buffer.flip();
1234567
第三步:把缓冲区数据写入通道中
fc.write( buffer );
12
3.3 读写结合
CopyFile是一个非常好的读写结合的例子,我们将通过CopyFile这个实力让大家体会NIO的操作过程。CopyFile执行三个基本的操作:创建一个Buffer,然后从源文件读取数据到缓冲区,然后再将缓冲区写入目标文件。
/**
* 用java NIO api拷贝文件
* @param src
* @param dst
* @throws IOException
*/
public static void copyFileUseNIO(String src,String dst) throws IOException{
//声明源文件和目标文件
FileInputStream fi=new FileInputStream(new File(src));
FileOutputStream fo=new FileOutputStream(new File(dst));
//获得传输通道channel
FileChannel inChannel=fi.getChannel();
FileChannel outChannel=fo.getChannel();
//获得容器buffer
ByteBuffer buffer=ByteBuffer.allocate(1024);
while(true){
//判断是否读完文件
int eof =inChannel.read(buffer);
if(eof==-1){
break;
}
//重设一下buffer的position=0,limit=position
buffer.flip();
//开始写
outChannel.write(buffer);
//写完要重置buffer,重设position=0,limit=capacity
buffer.clear();
}
inChannel.close();
outChannel.close();
fi.close();
fo.close();
}
12345678910111213141516171819202122232425262728293031323334
四、需要注意的点
上面程序中有三个地方需要注意
4.1 检查状态
当没有更多的数据时,拷贝就算完成,此时 read() 方法会返回 -1 ,我们可以根据这个方法判断是否读完。
int r= fcin.read( buffer );
if (r==-1) {
break;
}
12345
4.2 Buffer类的flip、clear方法
控制buffer状态的三个变量
- position:跟踪已经写了多少数据或读了多少数据,它指向的是下一个字节来自哪个位置
- limit:代表还有多少数据可以取出或还有多少空间可以写入,它的值小于等于capacity。
- capacity:代表缓冲区的最大容量,一般新建一个缓冲区的时候,limit的值和capacity的值默认是相等的。
flip、clear这两个方法便是用来设置这些值的。
flip方法
我们先看一下flip的源码:
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
在上面的FileCopy程序中,写入数据之前我们调用了buffer.flip();
方法,这个方法把当前的指针位置position设置成了limit,再将当前指针position指向数据的最开始端,我们现在可以将数据从缓冲区写入通道了。 position 被设置为 0,这意味着我们得到的下一个字节是第一个字节。 limit 已被设置为原来的 position,这意味着它包括以前读到的所有字节,并且一个字节也不多。
clear方法
先看一下clear的源码:
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
在上面的FileCopy程序中,写入数据之后也就是读数据之前,我们调用了 buffer.clear();
方法,这个方法重设缓冲区以便接收更多的字节。上图显示了在调用 clear() 后缓冲区的状态。
转载请说明出处,原文链接:http://blog.csdn.net/suifeng3051/article/details/48160753
Java NIO 详解(一)的更多相关文章
- Java NIO 详解(二)
异步IO 异步 I/O 是一种没有阻塞地读写数据的方法.通常,在代码进行 read() 调用时,代码会阻塞直至有可供读取的数据.同样, write()调用将会阻塞直至数据能够写入,关于同步的IO请参考 ...
- java NIO 详解
Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API.本系列教程将有助于你学习和理解Java NIO. Java NIO提供了与 ...
- [转载]java NIO详解
Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API.下面的文章写的很详细,还配有插图,有助于深入学习和理解java NIO 文 ...
- java NIO详解
http://zalezone.cn/2014/09/17/NIO%E7%B2%BE%E7%B2%B9/ 1. 前言 我们在写java程序的时候,为了进行优化,把全部的精力用在了处理效率上,但是对IO ...
- Java网络编程和NIO详解开篇:Java网络编程基础
Java网络编程和NIO详解开篇:Java网络编程基础 计算机网络编程基础 转自:https://mp.weixin.qq.com/s/XXMz5uAFSsPdg38bth2jAA 我们是幸运的,因为 ...
- Java网络编程和NIO详解8:浅析mmap和Direct Buffer
Java网络编程与NIO详解8:浅析mmap和Direct Buffer 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Java网络编程和NI ...
- Java网络编程和NIO详解9:基于NIO的网络编程框架Netty
Java网络编程和NIO详解9:基于NIO的网络编程框架Netty 转自https://sylvanassun.github.io/2017/11/30/2017-11-30-netty_introd ...
- Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理
Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理 转自:https://www.jianshu.com/p/2b71ea919d49 本系列文章首发于我的个人博 ...
- Java网络编程和NIO详解6:Linux epoll实现原理详解
Java网络编程和NIO详解6:Linux epoll实现原理详解 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Java网络编程和NIO h ...
随机推荐
- Working With JSON
JavaScript对象表示法(JSON)是用于将结构化数据表示为JavaScript对象的标准格式,通常用于在网站上表示和传输数据(例如从服务器向客户端发送一些数据,因此可以将其显示在网页上). J ...
- android asmack调用MultiUserChat.getHostedRooms方法出现空指针的异常解决方案
今天在做即时通讯群聊时,调用MultiUserChat.getHostedRooms(conn, SmackTools.getInstance().conn.getServiceName());方法获 ...
- Android 调用手机上第三方百度地图并传值给地图
//移动APP调起Android百度地图方式举例 Intent intent = null; try { // intent = Intent.getIntent("intent://map ...
- JAVA之多线程概念及其几种实现方法优劣分析
1. 多线程 程序:指令集,静态的概念 进程:操作系统调动程序,是程序的一次动态执行过程,动态的概念 线程:在进程内的多条执行路径 Ps:单核的话进程都是虚拟模拟出来的,多核处理器才可以执行真正的多线 ...
- array_reduce()使用
这个函数的作用是,把数组中的值循环放到回调函数里处理,结果返回一个单一的值.(applies iteratively the callback function to the elements of ...
- MyBatis在Oracle中插入数据并返回主键的问题解决
引言: 在MyBatis中,希望在Oracle中插入数据之时,同一时候返回主键值,而非插入的条数... 环境:MyBatis 3.2 , Oracle. Spring 3.2 SQL Snipp ...
- mfc 线程的诞生和死亡
知识点: 线程概念 线程的诞生 线程的死亡 一. 线程: 线程,是程序执行流的最小单元. 另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点 ...
- scala学习——(1)scala基础(上)
scala> val x = 1 x: Int = 1 一.值与变量 值(val):赋值后不可变 val值名称:类型 = XXX 变量(var):赋值后可以改变 var变量名称:类型 = XX ...
- [摘抄]从 GitHub 身上学到的 3 个创业经验
1.找一个大问题去解决 让 Git 更容易使用是 GitHub 的目标,但它从来不是 GitHub 的最终目标.GitHub 的真正目标是让协作和编写软件变得更容易.世界上每一个软件开发者都在努力解决 ...
- 菜鸟凉经(华为、firehome、大华)
面试通知都是前一天来的,准备的时间很少,所以表现也不是特别满意,来看面经吧: 华为一面(IT应用工程师): 1.自我介绍:(华为面试都是1对1,面前的是个温柔的小哥,挺放松的) 2.你主要会的it技术 ...