目录
知识储备(Java IO)
NIO概述
1. 定义
- Java NIO,即Java New IO(Java新IO)。
- 是一种同步非阻塞的IO模型。
- 自JDK1.4起,引入的一种基于通道和缓冲区的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在Java 堆的 DirectByteBuffer 对象作为这块内存的引用进行操作,避免了在 Java 堆和 Native堆中来回复制数据。
2. 作用
- 提供了与标准IO不同的IO工作方式
- 可替代 标准Java IO 的IO API
3. 新特性
4. 核心组件
- 缓冲区(Buffer)
- 通 道(Channel)
- 选择器(Selector)
Buffer
1. 定义
Java NIO数据读/写的中转地(一块连续的内存块)。从内部结构上看,它就像一个数组,可以保存多个类型相同的数据。
2. 作用
数据缓存
3. 特点
适用于所有基本数据类型(布尔除外)
4. 使用
1)给Buffer分配空间
例如:以字节为单位,创建一个ByteBuffer对象并且指定内存大小:
ByteBuffer buffer = ByteBuffer.allocate(1024);
除了ByteBuffer外,对应于其他基本数据类型(布尔除外)都有相应的Buffer类:CharBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer。区别在于:各子管理的数据类型不一样。
2)向Buffer中写入数据
- 数据从Channel到Buffer:channel.read(buffer);
- 数据从client到Buffer:buffer.put(……);
3)从Buffer中读取数据
- 数据从Buffer到Channel:channel.write(buffer);
- 数据从Buffer到Server:buffer.get(……);
Buffer中重要方法:
- clear():为再次向Buffer中装入数据做好准备,清空整个缓冲区。
- compact():只清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。
- flip():为从Buffer中取出数据做好准备,即将Buffer从写模式切换到读模式。
Buffer中的三个重要属性
- capacity:作为一个内存块,Buffer的固定大小值。只能往这里写capacity个基本数据类型的数据(布尔除外)。当Buffer满了,需要将其清空才能继续往里写数据。
- position:在写数据到Buffer中时,position标识表示当前的位置,初始值为0。当将数据写到Buffer中后,position会向前移动到下一个可插入数据的Buffer单元。position最大可为-1。当读取数据时,也是从某个特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0. 当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。
- limit:在写模式下,limit表示最多能往Buffer中写的数据量,limit等于capacity;在读模式下,limit表示最多能读到的数据量,此时,limit会被设置成写模式下的position值。
position和limit的含义取决于Buffer处在读模式还是写模式。不管Buffer处在什么模式,capacity的含义总是一样的。
Buffer的使用
public static void main(String[] args) {
//1.创建Buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
System.out.println("capacity:"+byteBuffer.capacity()+" position:"+byteBuffer.position()+" limit:"+byteBuffer.limit());
//2.向Buffer中写入数据
byte b1 = 10;
byte b2 = 20;
byteBuffer.put(b1);
byteBuffer.put(b2);
System.out.println("加入二个元素后--");
System.out.println("capacity:"+byteBuffer.capacity()+" position:"+byteBuffer.position()+" limit:"+byteBuffer.limit());
//3.调用flip()方法
byteBuffer.flip();
System.out.println("执行flip()方法后--");
System.out.println("capacity:"+byteBuffer.capacity()+" position:"+byteBuffer.position()+" limit:"+byteBuffer.limit());
//4.取出元素
System.out.println("第一个元素:"+byteBuffer.get());
//5.调用clear()方法
byteBuffer.clear();
System.out.println("调用clear()方法后--");
System.out.println("capacity:"+byteBuffer.capacity()+" position:"+byteBuffer.position()+" limit:"+byteBuffer.limit());
}
Channel
1. 定义
Java NIO的数据源头或者目的地,是缓冲区对象的唯一接口。
2. 作用
- 从缓冲区读取数据
- 向缓冲区写数据
3.特点
- 是一个双向读、写通道。既可以从通道中读取数据,又可以将数据写入通道。
- 异步读、写。
- 数据来源/流向是缓冲区。即通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。
4.使用
根据数据来源的不同而不同。(含文件IO、TCP、UDP等)
- FileChannel 从文件中读写数据。
- DatagramChannel 能通过UDP读写网络中的数据。
- SocketChannel 能通过TCP读写网络中的数据。
- ServerSocketChannel可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。
Channel的使用:
/*复制文件*/
/*直接将FileChannel数据映射成ByteBuffer*/
public static void main(String[] args) {
File f = new File("E:\\temps\\FileFilterDemos\\aa.txt");
try {
//创建FileInputStream,以该文件输入流创建FileChannel
FileChannel inChannel = new FileInputStream(f).getChannel();
FileChannel outChannel = new FileOutputStream("E:\\temps\\FileFilterDemos\\temp1.txt").getChannel();
//将FileChannel里的全部数据映射成ByteBuffer
MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, f.length());
//使用GBK的字符集来创建解码器
Charset charset = Charset.forName("GBK");
//直接将Buffer里面的数据直接输出
outChannel.write(buffer);
//调用Buffer的clear()方法,复原limit、position值
buffer.clear();
} catch (Exception e) {
e.printStackTrace();
}
}
Selector
1.定义
是NIO的核心,是channel的管理者。通过执行select()阻塞方法,监听是否有channel准备好,一旦有数据可读,此方法的返回值是SelectionKey的数量。
2. 作用
实现异步、非阻塞操作。
3.特点
-
允许一个Selector管理/处理多个通道Channel。
-
使用一个Selector线程检测1个或者多个通道上的事件(channel)时,事件驱动分发事件,而不需要为每个channel分配一个线程。
注意:事件驱动=事件到的时候出发,而不是同步监视事件。
4.Selector的使用
1)创建selector对象
Selector selector = Selector.open();
2) 向selector注册通道channel
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, Selectionkey.OP_READ);
注意:
- 与Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式。而套接字通道都可以。
- register()方法的第二个参数是一个“interest集合”,意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件:
Connect;Accept;Read;Write
用SelectionKey的四个常量来表示为:
SelectionKey.OP_CONNECT;
SelectionKey.OP_ACCEPT;
SelectionKey.OP_READ;
SelectionKey.OP_WRITE;
3) 调用Selector的select()方法
该方法会一直阻塞到某个注册的通道有事件就绪。一旦该方法返回,线程就可以处理这些事件。
int readChannel = selector.select();
- select()阻塞到至少有一个通道在你注册的事件上就绪了。
- select(long timeout)和select()一样,除了最长会阻塞timeout毫秒(参数)。
- selectNow()不会阻塞,不管什么通道就绪都立刻返回(译者注:此方法执行非阻塞的选择操作。如果自从前一次选择操作后,没有通道变成可选择的,则此方法直接返回零)。
selectedKeys():通过调用selector的selectedKeys()方法,访问“已选择键集(selected key set)”中的就绪通道。