I/O简介
I/O问题可以说是当今web应用中所面临的的主要问题之一,大部分的web应用系统的瓶颈都是I/O瓶颈。这个系列主要介绍JAVA的I/O类库基本架构、磁盘I/O工作机制、网络I/O工作机制以及NIO的工作方式。
BIO(Block IO)和Nio(Non-Block IO)的对比
IO模式 | BIO | NIO |
方式 | 从磁盘到磁盘 | 从缓存到磁盘 |
通信 | 流 | 缓存(多级复用) |
处理 | 阻塞 | 非阻塞(Reactor) |
触发 | 无 | 选择器轮询机制 |
从1.4版本开始JAVA引入了NIO,用来提升I/O性能。I/O操作类在包java.io下,大概有将近80个类
这些类可以分为如下四组:
基于字节操作的I/O接口:InputStream和OutputStream
基于字符操作的I/O接口:Reader和Writer
基于磁盘操作的I/O接口:File
基于网络操作的I/O接口:Socket
前两组是传输数据的格式,后两组是传输数据的方式
核心组件
核心组件 | 定义 | 作用 | 特点 | 使用 |
通道 (Channel) | NIO数据的源头/目的地 (Buffer的唯一接口) | 向缓存提供数据 读取换区的数据 | 双向读写,异步读写 数据来源/流向 总是Buffer | 根据来源区别: FileChannel:从文件 DataChannel:从UDP网络数据 SocketChannel: ServerSocketChannel |
缓存 (Buffer) | NIO数据读/写中转地 (一块完整的内存块) | 数据缓存 | 适用于处布尔外基本数据类型 | 7种基本数据类型
|
选择器 (Selector) | 异步IO核心类 | 实现异步非阻塞IO | 使用一个Selector线程检测1个/多个 通道channel上的事件,给予事件驱动 分发不需要为每个channel去分配一个线程 | 1、创建Selector对象 2、箱Slector注册通道Channel 2、调用Selector类中select()方法 |
一、Buffer缓存区
1) Buffer介绍: 缓冲区,本质就是一个数组,但是它是特殊的数组,缓冲区对象内置了一些机制,能够追踪和记录缓冲区的状态变化情况,如果我们使用get方法从 缓冲区中获取数据或者用put方法吧数据写入缓冲区,都会引起缓冲区的状态变化
在缓冲区中,最重要的属性是如下三个,他们一起合作完成了对缓冲区内容状态的变化跟踪
1)position:指定了下一个将要被写入或者读取的元素索引,它的值由get()/put() 方法自动更新,在新创建一个Buffer对象时,position被初始化为0
2)limit:操作缓冲区的可操作空间和可操作范围,指定还有多少数据需要去除,或者还有多少空间可以放入数据
3)capacity:指定了可以存储在缓冲区中的最大数据容量,实际上,它指定了底层数组的大小,或者至少是指定了准许我们使用的底层数组的容量。
以上三个属性值之间有一些相对的大小的关系:0<=position<=limit<=capacity
package com.Allen.buffer; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class testBufferDemo01 { public static void main(String[] args) throws IOException { String fileURL="F://a.txt"; FileInputStream fis=new FileInputStream(fileURL); //获取通路 FileChannel channel=fis.getChannel(); //定义缓冲区大小 ByteBuffer buffer=ByteBuffer.allocate(10); output("init", buffer); //先读 channel.read(buffer); output("read", buffer); buffer.flip(); output("flip", buffer); while (buffer.hasRemaining()) { byte b=buffer.get(); } output("get", buffer); buffer.clear(); output("clear", buffer); fis.close(); } public static void output(String string,ByteBuffer buffer){ System.out.println(string); System.out.println(buffer.capacity()+":"+buffer.position()+":"+buffer.limit()); } }
二、Channel(通路)
任何时候读取数据,都不是直接从通道中读取,而是从通道读取到缓冲区,所以使用NIO读取数据可以分成下面三个步骤
1)从FileInputStream获取Channel
2)创建Buffer
3)将数据从Channel 读取到Buffer中
package com.allen.test; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class testNio { public static void main(String[] args) throws IOException { String oldFileUrl="E://1.txt"; String newFileUrl="E://2.txt"; FileInputStream fis=new FileInputStream(oldFileUrl);
//1、获取Channel FileChannel inChannel=fis.getChannel();
//2、创建Buffer ByteBuffer bf=ByteBuffer.allocate(1024); FileOutputStream fos=new FileOutputStream(newFileUrl); FileChannel outChannel=fos.getChannel(); while(true){ int eof=inChannel.read(bf); if(eof==-1){ break; }else{ bf.flip();
//3、把数据写入缓存 outChannel.write(bf); bf.clear(); } } inChannel.close(); fis.close(); outChannel.close(); fos.close(); } }
三、Selector(选择器)
// 1. 创建Selector对象 Selector sel = Selector.open(); // 2. 向Selector对象绑定通道 // a. 创建可选择通道,并配置为非阻塞模式 ServerSocketChannel server = ServerSocketChannel.open(); server.configureBlocking(false); // b. 绑定通道到指定端口 ServerSocket socket = server.socket(); InetSocketAddress address = new InetSocketAddress(port); socket.bind(address); // c. 向Selector中注册感兴趣的事件 server.register(sel, SelectionKey.OP_ACCEPT); return sel; // 3. 处理事件 try { while(true) { // 该调用会阻塞,直到至少有一个事件就绪、准备发生 selector.select(); // 一旦上述方法返回,线程就可以处理这些事件 Set<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> iter = keys.iterator(); while (iter.hasNext()) { SelectionKey key = (SelectionKey) iter.next(); iter.remove(); process(key); } } } catch (IOException e) { e.printStackTrace(); }
总结nio工作原理
1)由一个专门的线程去处理所有的io事件并且负责分发2)事件驱动机制,时间到的时候触发,而不是同步地去监听事件
3)线程通信,线程之间通过wait,notify等方式通信,保证每次上下文切换都是有意义的,减少无畏的线程切换。