Java NIO学习系列六:Java中的IO模型
前文中我们总结了linux系统中的5中IO模型,并且着重介绍了其中的4种IO模型:
- 阻塞I/O(blocking IO)
- 非阻塞I/O(nonblocking IO)
- I/O多路复用(IO multiplexing)
- 异步I/O(asynchronous IO)
但是前面总结的IO模型只是限定在linux下,更偏向于操作系统底层的概念,并没有涉及到Java应用层面,其实Java中也提供了和前面操作系统层面的IO模型相对应的概念,这是本文接下来要讲的重点。
同样本文会围绕如下几点进行展开:
1. I/O模型在Java中的对应
1.1 阻塞I/O
传统Java IO提供的面向流的IO操作方式就属于阻塞式的,调用其read()或write()方法的线程会阻塞,直到完成了数据的读写,在读写的过程中线程是什么都做不了的。
1.2 非阻塞I/O
Java NIO类库提供了多种支持非阻塞模式的类,比如Channel、Buffer,可以将其设置为非阻塞模式,线程向channel请求读数据时,只会获取已经就绪的数据,并不会阻塞以等待所有数据都准备好,这样在数据准备的阶段线程就能够去处理别的事情,这就是非阻塞式读,对于写数据是一样的。
这里和上面阻塞的区别就是,调用read()或write()方法并不阻塞,而是会立即返回,但是这时候IO操作往往是还没有结束的。
1.3 多路复用
Java NIO中的Selector允许单个线程监控多个channel,可以将多个channel注册到一个Selector中,然后可以"select"出已经准备好数据的channel,或者准备好写入的channel,然后对其进行读或者写数据,这就是多路复用。
1.4 异步IO
异步IO模型是比较理想的IO模型,在异步IO模型中,当用户线程发起read操作之后,立刻就可以开始去做其它的事。另一方面,内核会等待数据准备完成,然后将数据复制到用户线程,当这一切都完成之后,内核会给用户线程发送一个信号,告诉它read操作完成了。也就是说用户线程完全不需要关心实际的整个IO操作了,只需要发起请求就行了,当收到内核的成功信号时就可以直接去使用数据了。这就是和非阻塞式的区别,如果说阻塞式IO是完全手动,非阻塞式IO就是半自动,而异步IO就是全自动,多路复用呢?我觉得可以是半自动冲锋枪^_^
在Java 7中,提供了Asynchronous IO,Java NIO中的AsynchronousFileChannel支持异步模型实现的。
2. 适用场景
BIO方式适用于连接数目比较小且每个连接占用大量宽带,这种方式对服务器资源要求比较高,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,编程比较复杂,JDK1.4开始支持。
AIO方式适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
3. Java中各种IO模型的使用方式
前面讲了这么多,即讲了linux下的IO模型,又讲了Java中对这些IO模型的支持,到这里我觉得是时候找一些Java中实际的例子看看,下面就分别用三种IO模型来读写文件。
3.1 通过BIO方式读写文件
public void rwByBIO() {
BufferedReader br = null;
FileInputStream in = null;
FileOutputStream out = null;
try {
in = new FileInputStream("test.txt");
out = new FileOutputStream("testBIO.txt");
List<Integer> list = new ArrayList();
int temp;
while((temp = in.read()) != -1) {
out.write(temp);
}
br = new BufferedReader(new InputStreamReader(new FileInputStream("testBIO.txt")));
System.out.println(br.readLine());
}catch(Exception e) {
e.printStackTrace();
}finally {
if(br != null) {
try {
br.close();
}catch(IOException e) {
e.printStackTrace();
}
}
if(out != null) {
try {
out.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
}
在根目录下准备好文件test.txt,里面写上准备好的内容,比如"黄沙百战穿金甲,不破楼兰终不还",然后跑起来,之后应该会多出一个文件testBIO.txt,里面内容是一样的。我们通过BIO的方式读取test.txt中的内容,同样以BIO的方式写入到testBIO.txt中。
3.2 通过NIO读写文件
public void rwByNIO() {
FileChannel readChannel = null;
FileChannel writeChannel = null;
try {
readChannel = new RandomAccessFile(new File("test.txt"),"r").getChannel();
writeChannel = new RandomAccessFile(new File("testNIO.txt"),"rw").getChannel();
ByteBuffer buffer = ByteBuffer.allocate(10);
int bytesRead = readChannel.read(buffer);
while(bytesRead != -1) {
buffer.flip();
while(buffer.hasRemaining()) {
// 写入文件
writeChannel.write(buffer);
}
// 一次写完之后
buffer.clear();
bytesRead = readChannel.read(buffer);
}
}catch(Exception e) {
e.printStackTrace();
}finally {
if(readChannel != null) {
try {
readChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(writeChannel != null) {
try {
writeChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
这里是通过NIO中的FileChannel来读写文件,但是要注意,虽然这一节的标题是说用NIO的方式来读写文件,但是FileChannel并不支持非阻塞模式,所以其实际上还是属于阻塞的,即BIO的方式,只是因为这里为了统一演示读写文件的例子,所以仍然使用NIO中的FileChannel类来完成。
3.3 通过AIO方式读写文件
public void rwByAIO() {
Path path = Paths.get("test.txt");
AsynchronousFileChannel fileChannel = null;
try {
fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);
ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;
Future<Integer> operation = fileChannel.read(buffer, position);
while(!operation.isDone());
buffer.flip();
Path writePath = Paths.get("testAIO.txt");
if(!Files.exists(writePath)){
Files.createFile(writePath);
}
AsynchronousFileChannel writeFileChannel = AsynchronousFileChannel.open(writePath, StandardOpenOption.WRITE);
writeFileChannel.write(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
System.out.println("bytes written: " + result);
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.out.println("Write failed");
exc.printStackTrace();
}
});
}catch(Exception e) {
e.printStackTrace();
}
}
这个例子中是通过异步地方式来读写文件。当调用了Java NIO中的AsynchronousFileChannel对这种操作提供了支持,当调用其read()方法时会立即返回一个Future对象,通过调用其isDone方法来得知数据是否读取完毕。
4. 总结
本文结合前文讲到的IO模型,分别对应到Java中的具体类库实现,并通过例子演示了BIO、NIO、AIO三种方式读写文件。
- 标准Java IO提供的面向流的方式属于BIO模型的实现,在读取的过程中是会阻塞的;
- Java NIO提供的Channel和Buffer是支持NIO模式的,调用了Channel的读写方法之后可以立即返回,在往Buffer中准备数据的过程中是不阻塞的,线程可以做别的事情,但是从Buffer读写数据是阻塞的;
- Java NIO中提供的AsynchronousFileChannel支持异步读写文件,当调用了其读写方法之后可以立即返回,只需要等待系统把数据复制到指定位置即可,整个过程都不会阻塞;
参考文献
Java NIO学习系列六:Java中的IO模型的更多相关文章
- Java NIO学习系列四:NIO和IO对比
前面的一些文章中我总结了一些Java IO和NIO相关的主要知识点,也是管中窥豹,IO类库已经功能很强大了,但是Java 为什么又要引入NIO,这是我一直不是很清楚的?前面也只是简单提及了一下:因为性 ...
- Java NIO学习系列七:Path、Files、AsynchronousFileChannel
相对于标准Java IO中通过File来指向文件和目录,Java NIO中提供了更丰富的类来支持对文件和目录的操作,不仅仅支持更多操作,还支持诸如异步读写等特性,本文我们就来学习一些Java NIO提 ...
- Java NIO学习笔记六 SocketChannel 和 ServerSocketChannel
Java NIO SocketChannel Java NIO SocketChannel是连接到TCP网络socket(套接字)的通道.Java NIO相当于Java Networking的sock ...
- Java NIO学习笔记一 Java NIO概述
Java NIO概述 Java NIO(新的IO)是Java的替代IO API(来自Java 1.4),这意味着替代标准的 java IO和java Networking API.Java NIO提供 ...
- Java NIO学习系列一:Buffer
前面三篇文章中分别总结了标准Java IO系统中的File.RandomAccessFile.I/O流系统,对于I/O系统从其继承体系入手,力求对类数量繁多的的I/O系统有一个清晰的认识,然后结合一些 ...
- Java NIO学习系列三:Selector
前面的两篇文章中总结了Java NIO中的两大基础组件Buffer和Channel的相关知识点,在NIO中都是通过Channel和Buffer的协作来读写数据的,在这个基础上通过selector来协调 ...
- Java NIO学习系列五:I/O模型
前面总结了很多IO.NIO相关的基础知识点,还总结了IO和NIO之间的区别及各自适用场景,本文会从另一个视角来学习一下IO,即IO模型.什么是IO模型?对于不同人.在不同场景下给出的答案是不同的,所以 ...
- Java NIO学习系列二:Channel
上文总结了Java NIO中的Buffer相关知识点,本文中我们来总结一下它的好兄弟:Channel.上文有说到,Java NIO中的Buffer一般和Channel配对使用,NIO中的所有IO都起始 ...
- CSS学习系列1 - CSS中的盒子模型 box model
css中有一个盒子模型的概念. 主要是用来告诉浏览器如何来计算页面元素的宽度和高度, 比如该元素的宽度/高度 是否包括内边距,边框,外边距. 盒子模型有一个属性box-sizing属性来说明是否包括 ...
随机推荐
- Laravel --- artisan创建表以及填充表数据流程总结
1.创建建表文件 php artisan make:migration create_comments_table 打开database/migrations/xxx_create_comments_ ...
- 解决安装Oracle本地可以访问客户端不能访问
现象:本地需要修改监听为localhost -->win+r--> sqlplus system/123@xxdb 可以登陆,远程客户端不能登陆:需要将监听修改为IP地址,重启监听:远程可 ...
- 《菜鸟也要学会C》-和大家聊一聊
简介 为什么要出本系列作品? 怎么学好C? 学完这套课程后,我的编程会怎么样? 1.1为什么要出本系列作品? 随着大部分人喜欢编程,大部分人都有一个毛病,就是想要急切的学完编程.其实这种思想是错误的, ...
- Python自学day-5
电影推荐: 阿甘正传 辛德勒名单 肖申克的救赎 勇敢的心 角斗士 美国丽人 教父 指环王 钢琴师 血钻 战争之王 ...
- Scala 学习之路(四)—— 数组Array
一.定长数组 在Scala中,如果你需要一个长度不变的数组,可以使用Array.但需要注意以下两点: 在Scala中使用(index)而不是[index]来访问数组中的元素,因为访问元素,对于Scal ...
- JAVA复习笔记03(完)
31.类中可定义接口 一个定义接口的java文件中最多有1个Public的接口 32.TreeMap 按照键值升序排序 LinkedHashMap 按照插入顺序排序 Map的操作: Map<in ...
- c++ 子类,基类 中this指针 虚函数使用
笔记: 子类和基类 构造函数不显式时,的this指针相同..在QT中,如果父类基于QObject,那么构造子类时传入this指针,这样所有子类,父类,基类都是同一地址.delelater(),会del ...
- F#周报2019年第27期
新闻 介绍Femto--使用Fable绑定的自动化npm包解决方案 Babel 7.5.0发布,包含动态导入与F#管道 iOS 13预览版发布 视频及幻灯片 Fabulous--F#用于跨平台移动应用 ...
- POI 身份证号码 手机号 日期值的处理方式
private static SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); ...
- 使用@Transactional注意的问题
@Transactional 基本原理概述 在应用系统调用声明@Transactional 的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,根据@ ...