这篇博客主要记录 Java NIO简介 、 Java NIO与IO的主要区别 , 缓冲区(Buffer)和通道(Channel) 、 文件通道(FileChannel) 、 NIO的非阻塞式网络通信 等。
一、Java NIO简介
Java NIO(New IO / Non-Block IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。
二、Java NIO与 IO 的主要区别
IO | NIO |
---|---|
面向流(Stream Oriented) | 面向缓冲区(Buffer Oriented) |
阻塞IO(Blocking IO) | 非阻塞IO(Non Blocking IO) |
(无触发器) | 选择器(Selectors) |
三、 缓冲区(Buffer)和通道(Channel)
Java NIO系统的核心在于:通道(Channel)和缓冲区(Buffer)。通道表示打开到 IO 设备(例如:文件、套接字)的连接。若需要使用 NIO 系统,需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。
简而言之呢,Channel负责传输,Buffer负责存储。
在学习缓冲区和通道之前呢,先了解一个基本的类——Path
NIO中的Path类相当于IO中的File类,方法也很相似,并且Path和File类可以互相转换。
package com.xyj.niostudy;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* 测试NIO中的Path类
*/
public class TestPath {
public static void main(String[] args) {
Path path=Paths.get("E:/MyJava/nio/test/demo.txt");
System.out.println(path);
//判断是否以某路径开始
boolean b = path.startsWith("E:/");
System.out.println(b);
//判断是否以某路径结尾
boolean c = path.endsWith("demo.txt");//.txt的话返回false,因为.txt充其量是后缀名,不是路径
System.out.println(c);
//判断是否是绝对路径
System.out.println(path.isAbsolute());
//返回文件名
System.out.println(path.getFileName());
//把Path对象转为File对象
File f = path.toFile();
System.out.println(f);
//再把File对象转成Path对象
Path path2 = f.toPath();
System.out.println(path2);
}
}
运行结果:
3.1 缓冲区(Buffer)
缓冲区(Buffer):一个用于特定基本数据类型的容器。由 java.nio 包定义的,所有缓冲区都是 Buffer 抽象类的子类。
Java NIO 中的 Buffer 主要用于与 NIO 通道进行交互,数据是从通道读入缓冲区,从缓冲区写入通道中的。
缓冲区(Buffer)
Buffer 就像一个数组,可以保存多个相同类型的数据。根据数据类型不同(boolean 除外) ,有以下 Buffer 常用子类:
- ByteBuffer
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
上述 Buffer 类 他们都采用相似的方法进行管理数据,只是各自管理的数据类型不同而已。都是通过如下方法获取一个 Buffer 对象:
static XxxBuffer allocate(int capacity) : 创建一个容量为 capacity 的 XxxBuffer 对象
缓冲区的基本属性
- 容量 (capacity) :表示 Buffer 最大数据容量,缓冲区容量不能为负,并且创建后不能更改。
- 限制 (limit):第一个不应该读取或写入的数据的索引,即位于 limit 后的数据不可读写。缓冲区的限制不能为负,并且不能大于其容量
- 位置 (position):下一个要读取或写入的数据的索引。缓冲区的位置不能为负,并且不能大于其限制
- 标记 (mark)与重置 (reset):标记是一个索引,通过 Buffer 中的 mark() 方法指定 Buffer 中一个特定的 position,之后可以通过调用 reset() 方法恢复到这个 position.
标记、位置、限制、容量遵守以下不变式: 0 <= mark <= position <= limit <= capacity
----缓冲区的基本属性图解:
非直接缓冲区与直接缓冲区:
缓冲区的代码详解:
package com.xyj.nio;
import java.nio.ByteBuffer;
import org.junit.Test;
/**
* 一、缓冲区(Buffer):在Java NIO中负责数据的存取。缓冲区就是数组。用于存储不同数据类型的数据。
* 根据数据类型不同(boolean 除外),有对应类型的缓冲区:
* ByteBuffer
* ShortBuffer
* CharBuffer
* IntBuffer
* LongBuffer
* FloatBuffer
* DoubleBuffer
*
* 上述缓冲区的管理方式几乎一致,通过allocate()获取指定大小的缓冲区
*
* 二、缓冲区存取数据的两个核心方法:
* put():存入数据到缓冲区中
* get():获取缓冲区中的数据
*
* 三、缓冲区中的四个核心属性
* capacity:容量,表示缓冲区中最大存储数据的容量。一旦声明不能改变。
* limit:界限,表示缓冲区中可以操作的数据的大小。(limit后面的空间不能读写)
* position:位置,表示缓冲区中正在操作数据的位置。(从0开始数)
*
* mark:标记,记录当前position的位置,可以通过reset()恢复到mark记录的位置
*
* 0<=mark<=position<=limit<=capacity
*
* 四、直接缓冲区与非直接缓冲区
* 非直接缓冲区:通过 allocate() 方法分配缓冲区,将缓冲区建立在JVM内存中
* 直接缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在OS的物理内存中,可以提高效率
*
*/
public class TestBuffer {
/**
* Buffer基本属性与基本方法测试
*/
@Test
public void test01() {
//1、分配一个指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
System.out.println("-----------------allocate()-------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//2、利用put()方法存入数据到缓冲区中
String str="abcde";
buf.put(str.getBytes());
System.out.println("-----------------put()-------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//3、切换成读取数据的模式
buf.flip();
System.out.println("-----------------flip()-------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//4、利用get方法读取缓冲区中的数据
byte [] dest=new byte[buf.limit()];
buf.get(dest);
System.out.println("读取的数据:"+new String(dest,0,dest.length));
System.out.println("-----------------get()-------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//5、rewind():可重复读数据
buf.rewind();
System.out.println("-----------------rewind()-------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//6、清空缓冲区,但是缓冲区中的数据依然存在,处于“被遗忘”状态
buf.clear();
System.out.println("-----------------clear()-------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
System.out.println((char)buf.get());
}
/**
* mark与reset测试
*/
@Test
public void test02() {
String str="abcde";
ByteBuffer buf = ByteBuffer.allocate(1024);
buf.put(str.getBytes());
buf.flip();
byte [] dest=new byte[buf.limit()];
buf.get(dest,0,2);
System.out.println(new String(dest,0,2));
System.out.println(buf.position());
//mark():标记以下position的位置
buf.mark();
buf.get(dest,2,2);
System.out.println(new String(dest,2,2));
System.out.println(buf.position());
//reset():恢复到mark的位置
buf.reset();
System.out.println(buf.position());
//判断缓冲区中是否还有剩余的数据
if(buf.hasRemaining()) {
//如果有,获取缓冲区中可以操作的数量
System.out.println(buf.remaining());
}
}
/**
* 直接缓冲区
*/
@Test
public void test03() {
//分配直接缓冲区
ByteBuffer buf = ByteBuffer.allocateDirect(1024);
//判断是否是直接缓冲区
System.out.println(buf.isDirect());
}
}
test01单元测试运行结果:
test01单元测试运行结果:
test03单元测试运行结果: true
3.2通道(Channel)
通道(Channel):由 java.nio.channels 包定义的。Channel 表示 IO 源与目标打开的连接。Channel 类似于传统的“流”。只不过 Channel 本身不能直接访问数据,Channel 只能与Buffer 进行交互。
通道图示:
Java为Channel接口提供的最主要实现类如下:
- FileChannel:用于读取、写入、映射和操作文件的通道。
- SocketChannel:通过 TCP 读写网络中的数据。
- ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。
- DatagramChannel:通过 UDP 读写网络中的数据通道。
Channel代码详解:
package com.xyj.nio;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import org.junit.Test;
/**
* 一、通道(Channel):用于源节点与目标节点的连接,在Java NIO中负责缓冲区中数据的传输。通道本身不存储数据,需要配合缓冲区进行传输。
*
* 二、通道的主要实现类
* java.nio.channels.Channel
* --FileChannel
* --SocketChannel
* --ServerSocketChannel
* --DatagramChannel
*
*
* 三、获取通道
* 1、Java针对支持通道的类提供了getChannel()方法
* 本地IO:
* FileInputStream/FielOutputStream
* RandomAccessFile
*
* 网络IO:
* Socket
* ServerSocket
* DatagramSocket
*
* 2、在JDK1.7中的 NIO.2针对各个通道通道提供了静态方法open()
* 3、在JDK1.7中的 NIO.2的Files工具类的newByteChannel()方法
*
* 四、通道之间的数据传输
* transferFrom()
* transferTo()
*
* 五、分散(Scatter)与聚集(Gather)
* 分散读取(Scatter Reads):将通道中的数据分散到多个缓冲区中去。注意:按照缓冲区的顺序,从 Channel 中读取的数据依次将 Buffer 填满。
* 聚集写入(Gather Writes):将多个缓冲区中的数据聚集到通道中。 注意:按照缓冲区的顺序,写入 position 和 limit 之间的数据到 Channel 。
*
* 六、字符集 Charset
* 编码:字符串——>字节数组
* 解码:字节数组——>字符串
*
*
*/
public class TestChannel {
//1、利用通道完成文件的复制(非直接缓冲区)
@Test
public void test01() throws IOException {
FileInputStream fis=new FileInputStream("1.png");
FileOutputStream fos=new FileOutputStream("1copy.png");
//① 获取通道
FileChannel inChannel = fis.getChannel();
FileChannel outChannel = fos.getChannel();
//② 分配一个指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//③ 将通道中的数据存入缓冲区中
while((inChannel.read(buf))!=-1) {
//将缓冲区切换成读数据模式 然后读取出来写入通道
buf.flip();
//④ 将缓冲区中的数据写入通道中
outChannel.write(buf);
//清空缓冲区
buf.clear();
}
if(new File("1copy.png").exists()) {
System.out.println("复制成功!!!");
}
//关闭通道和流
outChannel.close();
inChannel.close();
fos.close();
fis.close();
}
//2、使用直接缓冲区(只有ByteBuffer支持)完成文件的复制(内存映射文件的方式)
@Test
public void test02() throws IOException {
FileChannel inChannel = FileChannel.open(Paths.get("2.png"),StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("2copy.png"),StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);//CREATE如果不存在就创建,存在就覆盖 CREATE_NEW不存在就创建,存在就报错
//内存映射文件 和allocateDirect()原理一模一样,只是获取方式不一样
MappedByteBuffer inMappedBuf = inChannel.map(MapMode.READ_ONLY,0,inChannel.size());
MappedByteBuffer outMappedBuf = outChannel.map(MapMode.READ_WRITE,0,inChannel.size());//因为这里的MapMode为READ_WRITE,所以上面outChannel的StandardOpenOption既要有READ又要有WRITE
//直接对缓冲区进行数据的读写操作
byte [] dest=new byte[inMappedBuf.limit()];
inMappedBuf.get(dest);
outMappedBuf.put(dest);
outChannel.close();
inChannel.close();
}
//2、使用直接缓冲区(只有ByteBuffer支持)完成文件的复制(ByteBuffer.allocateDirect()的方式)
@Test
public void test022() throws IOException {
FileChannel inChannel = FileChannel.open(Paths.get("2.png"),StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("2-copy.png"),StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);//CREATE如果不存在就创建,存在就覆盖 CREATE_NEW不存在就创建,存在就报错
//直接缓冲区
ByteBuffer buf = ByteBuffer.allocateDirect((int)inChannel.size());
while((inChannel.read(buf))!=-1) {
buf.flip();
outChannel.write(buf);
buf.clear();
}
outChannel.close();
inChannel.close();
}
//3、通道之间的数据传输(直接缓冲区)
@Test
public void test03() throws IOException {
FileChannel inChannel = FileChannel.open(Paths.get("1.png"),StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("1-copy.png"),StandardOpenOption.WRITE,StandardOpenOption.CREATE);
//两个都可以,任选其一
//inChannel.transferTo(0,inChannel.size(),outChannel);
outChannel.transferFrom(inChannel, 0,inChannel.size());
outChannel.close();
inChannel.close();
}
//4、分散(Scatter)与聚集(Gather)
@Test
public void test04() throws IOException {
RandomAccessFile raf1=new RandomAccessFile("1.txt","rw");
//1、获取通道
FileChannel channel1 = raf1.getChannel();
//2、分配指定大小的缓冲区
ByteBuffer buf1 = ByteBuffer.allocate(100);
ByteBuffer buf2 = ByteBuffer.allocate(1024);
//3、分散读取
ByteBuffer[] bufs= {buf1,buf2};
channel1.read(bufs);
for (ByteBuffer byteBuffer : bufs) {
byteBuffer.flip();
}
System.out.println(new String(bufs[0].array(),0,bufs[0].limit()));
System.out.println("******************************************");
System.out.println(new String(bufs[1].array(),0,bufs[1].limit()));
//4、聚集写入
RandomAccessFile raf2=new RandomAccessFile("1copy.txt","rw");
FileChannel channel2 = raf2.getChannel();
channel2.write(bufs);
channel2.close();
channel1.close();
}
//5、获取字符集
@Test
public void test05() {
//所有支持的字符集
SortedMap<String, Charset> map = Charset.availableCharsets();
Set<Entry<String, Charset>> set = map.entrySet();
for (Entry<String, Charset> entry : set) {
System.out.println(entry.getKey()+" -- "+entry.getValue());
}
}
//6、操作字符集
@Test
public void test06() throws CharacterCodingException {
Charset cs1 = Charset.forName("GBK");
//获取编码器
CharsetEncoder encoder = cs1.newEncoder();
//获取解码器
CharsetDecoder decoder = cs1.newDecoder();
CharBuffer cBuf = CharBuffer.allocate(1024);
cBuf.put("今天还有好多事要做!!!");
cBuf.flip();
//编码
ByteBuffer bBuf = encoder.encode(cBuf);
for(int i=0;i<21;i++) {
System.out.println(bBuf.get());
}
//解码
bBuf.flip();
CharBuffer cBuf2 = decoder.decode(bBuf);
System.out.println(cBuf2.toString());
System.out.println("-----------------------------------------------");
//获取utf-8的解码器
Charset cs2 = Charset.forName("utf-8");
bBuf.flip();
CharBuffer cBuf3 =cs2.decode(bBuf);
System.out.println(cBuf3.toString());
}
}
四、NIO的非阻塞式网络通信
阻塞与非阻塞: (阻塞与非阻塞面向网络通信)
- 传统的 IO 流都是阻塞式的。也就是说,当一个线程调用 read() 或 write() 时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务。因此,在完成网络通信进行 IO 操作时,由于线程会阻塞,所以服务器端必须为每个客户端都提供一个独立的线程进行处理,当服务器端需要处理大量客户端时,性能急剧下降。
- Java NIO 是非阻塞模式的。当线程从某通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道。因此,NIO 可以让服务器端使用一个或有限几个线程来同时处理连接到服务器端的所有客户端。
NIO中关于选择器的知识点:
在正式进行非阻塞式网络通信之前,先来回顾一下阻塞式网络通信 >>
简单的阻塞式网络通信1:
package com.xyj.nio2;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import org.junit.Test;
/**
* 阻塞式NIO
*
* 一、使用NIO完成网络通信的三个核心:
*
* FileChannel不能切换成非阻塞模式,非阻塞模式是相较于网络IO而言,所以选择器也是监控网络IO
* 1、通道(Channel):负责连接
* java.nio.channels.Channel接口:
* SelectableChannel抽象类
* SocketChannel
* ServerSocketChannel
* DatagramChannel
*
* Pipe.SinkChannel
* Pipe.SourceChannel
*
*
* 2、缓冲区(buffer):负责数据的存取
*
* 3、选择器(Selector):是SelectableChannel的多路复用器,用于监控SelectableChannel的IO状况
*
*
*
*/
public class TestBlockingNIO {
//客户端
@Test
public void client() throws IOException {
//1、获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8888));
FileChannel inChannel = FileChannel.open(Paths.get("1.png"),StandardOpenOption.READ);
//2、分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//3、读取本地文件并发送到服务端去
while((inChannel.read(buf))!=-1) {
buf.flip();
sChannel.write(buf);
buf.clear();
}
inChannel.close();
sChannel.close();
}
//服务端
@Test
public void server() throws IOException {
//1、获取通道
ServerSocketChannel serverChannel = ServerSocketChannel.open();
FileChannel outChannel = FileChannel.open(Paths.get("1-2.png"),StandardOpenOption.WRITE,StandardOpenOption.CREATE);
//2、绑定连接端口号
serverChannel.bind(new InetSocketAddress(8888));
//3、获取客户端连接的通道
SocketChannel sChannel = serverChannel.accept();
//4、分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//5、接收客户端的数据并保存到本地
while((sChannel.read(buf))!=-1) {
buf.flip();
outChannel.write(buf);
buf.clear();
}
sChannel.close();
serverChannel.close();
}
}
阻塞式网络通信2(接收服务端发来的反馈):
package com.xyj.nio2;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import org.junit.Test;
public class TestBlockingNIO2 {
//客户端
@Test
public void client() throws IOException {
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress(8888));
FileChannel inChannel = FileChannel.open(Paths.get("2.jpg"),StandardOpenOption.READ);
ByteBuffer buf = ByteBuffer.allocate(1024);
while(inChannel.read(buf)!=-1) {
buf.flip();
sChannel.write(buf);
buf.clear();
}
sChannel.shutdownOutput();//告知服务器端发送完毕 否则线程一直处于阻塞状态无法结束
//接收服务端的反馈
int len=-1;
while((len=sChannel.read(buf))!=-1) {
buf.flip();
System.out.println(new String(buf.array(),0,len));
buf.clear();
}
inChannel.close();
sChannel.close();
}
//服务端
@Test
public void server() throws IOException {
ServerSocketChannel ssChannel = ServerSocketChannel.open();
FileChannel outChannel = FileChannel.open(Paths.get("2-copy.jpg"),StandardOpenOption.WRITE,StandardOpenOption.CREATE);
ssChannel.bind(new InetSocketAddress(8888));
SocketChannel sCahnnel = ssChannel.accept();
ByteBuffer buf = ByteBuffer.allocate(1024);
while(sCahnnel.read(buf)!=-1) {
buf.flip();
outChannel.write(buf);
buf.clear();
}
//发送反馈给客户端
buf.put("服务端接收数据成功!!!".getBytes());
buf.flip();
sCahnnel.write(buf);
sCahnnel.close();
outChannel.close();
ssChannel.close();
}
}
以上就是阻塞式网络通信,接下来学习非阻塞式网络通信:
package com.xyj.nio2;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.time.LocalDateTime;
import java.util.Iterator;
import java.util.Scanner;
import org.junit.Test;
/**
* 非阻塞式NIO
*
* 一、使用NIO完成网络通信的三个核心:
*
* FileChannel不能切换成非阻塞模式,非阻塞模式是相较于网络IO而言,所以选择器也是监控网络IO
* 1、通道(Channel):负责连接
* java.nio.channels.Channel接口:
* SelectableChannel抽象类
* SocketChannel
* ServerSocketChannel
* DatagramChannel
*
* Pipe.SinkChannel
* Pipe.SourceChannel
*
*
* 2、缓冲区(buffer):负责数据的存取
*
* 3、选择器(Selector):是SelectableChannel的多路复用器,用于监控SelectableChannel的IO状况
*
*
*
*/
public class TestNonBlockingNIO {
//客户端
@Test
public void client() throws IOException {
//1、获取通道
SocketChannel clientChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8888));
//2、切换成非阻塞模式
clientChannel.configureBlocking(false);
//3、分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//4、发送数据给服务端
/*buf.put(LocalDateTime.now().toString().getBytes());
buf.flip();
clientChannel.write(buf);
buf.clear();*/
Scanner input=new Scanner(System.in);
while(input.hasNext()) {
String str=input.next();
buf.put((LocalDateTime.now().toString()+"\n"+str).getBytes());
buf.flip();
clientChannel.write(buf);
buf.clear();
}
//5、关闭通道
clientChannel.close();
}
//服务端
@Test
public void server() throws IOException {
//1、获取通道
ServerSocketChannel serverChannel = ServerSocketChannel.open();
//2、切换成非阻塞模式
serverChannel.configureBlocking(false);
//3、绑定连接
serverChannel.bind(new InetSocketAddress(8888));
//4、获取选择器
Selector selector = Selector.open();
//5、将通道注册到选择器上,并且指定 监听事件
serverChannel.register(selector,SelectionKey.OP_ACCEPT);
//6、轮询式的获取选择器上已经准备就绪的事件
while(selector.select()>0) {
//7、获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
Iterator<SelectionKey> ite = selector.selectedKeys().iterator();
while(ite.hasNext()) {
//8、获取准备“就绪”的事件
SelectionKey key = ite.next();
//9、判断具体是什么事件准备就绪
if(key.isAcceptable()){//接收事件就绪
//10、若接收就绪,获取客户端连接
SocketChannel clientChannel = serverChannel.accept();
//11、切换非阻塞模式
clientChannel.configureBlocking(false);
//12、将该通道注册到选择器上
clientChannel.register(selector,SelectionKey.OP_READ);
}else if(key.isReadable()){
//13、虎丘当前选择器上“读就绪”状态的通道
SocketChannel sChannel = (SocketChannel) key.channel();
//14、读取数据
ByteBuffer buf = ByteBuffer.allocate(1024);
int len=-1;
while((len=sChannel.read(buf))>0){
buf.flip();
System.out.println(new String(buf.array(),0,len));
buf.clear();
}
}
//15、取消选择键SelectionKey
ite.remove();
}
}
}
}
运行结果:
当然由于是非阻塞式通信,也可以开很多个客户端,类似于一个简陋的聊天室
上面的是非阻塞式的TCP通信,那么非阻塞式的UDP通信怎么做呢:
package com.xyj.nio2;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.time.LocalDateTime;
import java.util.Iterator;
import java.util.Scanner;
import org.junit.Test;
/**
* 非阻塞式UDP
* @author Administrator
*
*/
public class TestNonBlockingNIO2 {
//发送端
@Test
public void send() throws IOException {
DatagramChannel dc = DatagramChannel.open();
dc.configureBlocking(false);
ByteBuffer buf = ByteBuffer.allocate(1024);
Scanner input=new Scanner(System.in);
while(input.hasNext()) {
String str=input.next();
buf.put((LocalDateTime.now().toString()+":\n"+str).getBytes());
buf.flip();
dc.send(buf,new InetSocketAddress("127.0.0.1",8888));
buf.clear();
}
dc.close();
}
//接收端
@Test
public void receive() throws IOException {
DatagramChannel dc = DatagramChannel.open();
dc.configureBlocking(false);
dc.bind(new InetSocketAddress(8888));
Selector selector = Selector.open();
dc.register(selector,SelectionKey.OP_READ);
while(selector.select()>0) {
Iterator<SelectionKey> ite = selector.selectedKeys().iterator();
while(ite.hasNext()) {
SelectionKey key = ite.next();
if(key.isReadable()) {
ByteBuffer buf = ByteBuffer.allocate(1024);
dc.receive(buf);
buf.flip();
System.out.println(new String(buf.array(),0,buf.limit()));
buf.clear();
}
}
ite.remove();
}
}
}
五、管道(Pipe)
Java NIO 管道是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。
从管道读写数据:
package com.xyj.nio;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;
import java.nio.channels.Pipe.SinkChannel;
import java.nio.channels.Pipe.SourceChannel;
import org.junit.Test;
public class TestPipe {
@Test
public void test1() throws IOException {
//1、获取管道
Pipe pipe = Pipe.open();
//2、将缓冲区中的数据写入管道
SinkChannel sinkChannel = pipe.sink();
ByteBuffer buf = ByteBuffer.allocate(1024);
buf.put("通过单向管道发送数据".getBytes());
buf.flip();
sinkChannel.write(buf);
//3、读取缓冲区中的数据
SourceChannel sourceChannel = pipe.source();
buf.flip();
int len = sourceChannel.read(buf);
System.out.println(new String(buf.array(),0,len));;
sourceChannel.close();
sinkChannel.close();
//运行结果:通过单向管道发送数据
}
}
六、NIO2的Path、Paths、Files介绍
随着 JDK 7 的发布,Java对NIO进行了极大的扩展,增强了对文件处理和文件系统特性的支持,
以至于我们称他们为 NIO.2。因为 NIO 提供的一些功能,NIO已经成为文件处理中越来越重要的部分。
不再用代码展示。
最后介绍一个与NIO无关的语法糖,自动资源管理器,也叫try…with…resource
自动资源管理器
Java 7 增加了一个新特性,该特性提供了另外一种管理资源的方式,这种方式能自动关闭文件。这个特性有时被称为自动资源管理(Automatic Resource Management, ARM), 该特性以 try 语句的扩展版为基础。自动资源管理主要用于,当不再需要文件(或其他资源)时,可以防止无意中忘记释放它们。
简单的说呢就是替代了finally块中繁琐的关闭资源操作,可以自动帮助你去关闭,省去了finally块。前提呢就是,你所想自动关闭的资源,必须实现了AutoCloseabl接口或其自然接口Closeable.使用方式是把资源的完整声明写在try()括号中。
示例:
像上面图片那样,直接把声明写在try后面的括号中,多个声明用分号隔开。
终