Java NIO
文章目录
简介
什么是Java NIO
NIO :non-blocking IO (非阻塞IO)
Java NIO 是 Java1.4 引入的一个新的IO API
NIO与IO有同样的作用和目的,都是为了读写文件
NIO与IO的区别
NIO | IO |
---|---|
非阻塞IO | 阻塞IO |
面向缓冲区(双向) | 面向流(单向) |
选择器 |
总结:NIO支持面向缓冲区(Buffer),通道(channel)的IO操作,使用选择器来实现非阻塞IO,以更高效的方式进行文件的读写
下文将对NIO的三个重要的新名词:缓冲区(Buffer),通道(Channel),选择器(selector) 进行描述
通道与缓冲区
通道是打开IO设备的连接,缓冲区是用于容纳数据的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
通道用于传输,缓冲区用于存取
每个通道可以连接一个IO设备,所以读写文件一般使用两个通道
可以想象缓冲区运行在通道上,所以一般本地读写文件使用一个缓冲区即可
缓冲区(Buffer)
缓冲区的结构
java.nio包中有Buffer抽象类,它有着除boolean类型外的代表七个基本类型的子类
它们基本类似,只是各自管理的数据类型不同(后面使用ByteBuffer来举例)
实际上这些缓冲区使用数组来实现的
比如:ByteBuffer就是用byte[]来实现
缓冲区又分为直接缓冲区和非直接缓冲区
- 非直接缓冲区
-
直接缓冲区
非直接缓冲区:读取物理磁盘中的某个文件时,会把读到的数据放在内核地址中,然后经过copy到JVM的用户地址空间(写的过程类似)都需要一个copy的步骤
直接缓冲区:将缓冲区建立在物理内存中,通过一个存储在Java堆中的DirectByteBuffer
对象作为这块内存的引用进行操作,避免Java堆和物理内存来回复制数据
缓冲区的字段(属性)
Buffer中有四个重要的属性
// Invariants: mark <= position <= limit <= capacity
//标记索引,通过reset()回到此位置
private int mark = -1;
//操作下一个数据的索引
private int position = 0;
//分界线索引
private int limit;
//缓冲区总容量,因为底层是数组,所以不可变
private int capacity;
缓冲区有读,写两种模式,默认写,使用flip()方法切换成读模式
切换成读模式的时候,之前写到的最后位置就是limit分界线的位置
属性之间需要满足的关系:mark <= position <= limit <= capacity
缓冲区的方法
Buffer的子类提供了操作数据的两个重要方法:
- put():在缓冲区上写数据
- get():在缓冲区上读数据
其中有很多重载方法,方便多种实现
可以通过静态方法allocate()
来创建非直接缓冲区
public static ByteBuffer allocate(int capacity)
通过静态方法allocateDirect()
来创建直接缓冲区只有ByteBuffer有直接缓冲区(只有ByteBuffer有这个方法)
这个DirectByteBuffer
对象就是在堆中操作那块物理内存的引用
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
还可以通过FileChannel.map()
通道方法(后文会介绍)来创建DirectByteBuffer
的父类一个MappingByteBuffer
映射字节缓冲区(也是直接缓冲区)
public abstract MappedByteBuffer map(MapMode mode,
long position, long size)
throws IOException;
//模式,当前位置,总量
//模式:
/**只读
* Mode for a read-only mapping.
*/
public static final MapMode READ_ONLY
= new MapMode("READ_ONLY");
/**读写
* Mode for a read/write mapping.
*/
public static final MapMode READ_WRITE
= new MapMode("READ_WRITE");
/**写时复制
* Mode for a private (copy-on-write) mapping.
*/
public static final MapMode PRIVATE
= new MapMode("PRIVATE");
使用方法isDirect()
来判断此缓冲区是否是一个直接缓冲区
//创建非直接缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//创建直接缓冲区 直接缓冲区只能是Byte
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);
System.out.println("buffer.isDirect():buffer是直接缓冲区吗? "+buffer.isDirect());//false
System.out.println("directBuffer.isDirect():buffer是直接缓冲区吗? "+directBuffer.isDirect());//true
深入了解缓冲区的属性与方法
接下来看一段代码,详细的了解缓冲区的属性与方法
import org.junit.Test;
import java.nio.Buffer;
import java.nio.ByteBuffer;
/**
* @author Tc.l
* @Date 2020/10/24
* @Description: 缓冲区负责数据的存取(boolean类型无, 其他七大数据类型都有)
*/
public class _1缓冲区的数据读取 {
@Test
public void test() {
//allocate(): 新建一个容量为1024的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
lookFiled(buffer,"allocate(): 新建一个容量为1024的缓冲区");
//put(): 将数据存入缓冲区
String s = "abcdef";
buffer.put(s.getBytes());
lookFiled(buffer,"put(): 将数据abcdef存入缓冲区");
//flip() 切换读模式
buffer.flip();
System.out.println("切换为读模式");
//get(): 读缓冲区中的数据
byte[] bytes = new byte[buffer.limit()];
buffer.get(bytes,0,2);
lookFiled(buffer,"get(): 读2个缓冲区中的数据");
//读到的数据
System.out.print("读到的数据:");
for (byte b : bytes) {
if (b==0){
break;
}
System.out.print((char)b);
}
System.out.println();
//mark(): 标记位置
buffer.mark();
System.out.println("mark标记当前位置");
//get(): 读缓冲区中的数据
buffer.get(bytes,2,2);
lookFiled(buffer,"get(): 再读2个缓冲区中的数据");
//读到的数据
System.out.print("读到的数据:");
for (byte b : bytes) {
if (b==0){
break;
}
System.out.print((char)b);
}
System.out.println();
//reset(): 恢复到mark标记的位置
buffer.reset();
lookFiled(buffer,"reset(): 恢复到mark标记的位置");
//rewind():回到0位置
buffer.rewind();
lookFiled(buffer,"rewind():回到0位置");
}
private void lookFiled(Buffer buffer,String s) {
System.out.println();
System.out.println("================"+s+"======================");
System.out.println("position当前位置:" + buffer.position());
System.out.println("limit分界线位置:" + buffer.limit());
System.out.println("capacity总容量位置:" + buffer.capacity());
if (buffer.hasRemaining()){
System.out.println("remaining缓冲区中还可以操作的数量:"+buffer.remaining());
}
System.out.println();
}
}
输出内容:
/*
================allocate(): 新建一个容量为1024的缓冲区======================
position当前位置:0
limit分界线位置:1024
capacity总容量位置:1024
remaining缓冲区中还可以操作的数量:1024
================put(): 将数据abcdef存入缓冲区======================
position当前位置:6
limit分界线位置:1024
capacity总容量位置:1024
remaining缓冲区中还可以操作的数量:1018
切换为读模式
================get(): 读2个缓冲区中的数据======================
position当前位置:2
limit分界线位置:6
capacity总容量位置:1024
remaining缓冲区中还可以操作的数量:4
读到的数据:ab
mark标记当前位置
================get(): 再读2个缓冲区中的数据======================
position当前位置:4
limit分界线位置:6
capacity总容量位置:1024
remaining缓冲区中还可以操作的数量:2
读到的数据:abcd
================reset(): 恢复到mark标记的位置======================
position当前位置:2
limit分界线位置:6
capacity总容量位置:1024
remaining缓冲区中还可以操作的数量:4
================rewind():回到0位置======================
position当前位置:0
limit分界线位置:6
capacity总容量位置:1024
remaining缓冲区中还可以操作的数量:6
*/
通道(Channel)
通道的结构
通道用于连接IO设备,本身不能直接访问数据,只能与Buffer交互
通道基本都在java.nio.channels包下
- Channel接口四个重要的实现类
- FileChannel 读写操作文件的通道
- DatagramChannel 通过UDP读写网络中的数据通道
- SocketChannel 通过TCP读写网络中的数据
- ServerSocketChannel 服务端监听新来的TCP连接,为每个新连接创建一个SocketChannel
通道的方法
-
获得通道的三个方式
- 对支持通道的类,提供了
getChannel()
方法 - JDK 7 各个通道提供了open(Path path, OpenOption… options)静态方法
- JDK 7 工具类Files.newByteChannel(Path path, OpenOption… options)
- 对支持通道的类,提供了
-
通道的读写方法
-
Channel.read(Buffer) : 读通道的数据写到缓冲区中
- 对于通道来说: 这是在读
- 对于缓冲区来说: 这是在写
-
Channel.write(Buffer) :读缓冲区的数据写到通道中
- 对于通道来说: 这是在写
- 对缓冲区来说: 这是在读
因为对缓冲区来说在读,所以调用write()方法前,记得使用flip()让缓冲区切换为读模式
通道的read(),write()方法都是以通道作为本身来设计的
-
深入了解非直接与直接缓冲区的区别
接下来用一段代码进行本地文件的读写操作,并展示缓冲区,通道的使用
非直接与直接缓冲区+通道运输
/**
* 通道传输 + 非直接缓冲区
* 44秒
*/
@Test
public void test1() {
LocalDateTime start = LocalDateTime.now();
try (
//原文件
FileInputStream fis = new FileInputStream("D:\\QLDownload\\熊出没·奇幻空间\\熊出没·奇幻空间 熊出没奇幻空间 蓝光(1080P).qlv");
//目标文件
FileOutputStream fos = new FileOutputStream("D:\\QLDownload\\熊出没·奇幻空间\\2.qlv");
//获得连接目标文件的通道
FileChannel outChannel = fos.getChannel();
//获得连接源文件的通道
FileChannel inChannel = fis.getChannel();
) {
//创建非直接缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024);
//管道.read(缓冲区): 读管道中的数据写到缓冲区中
while ((inChannel.read(buffer) != -1)) {
//缓冲区切换为读模式
buffer.flip();
//管道.write(缓冲区): 读缓冲区的数据写到管道中
outChannel.write(buffer);
//清空缓冲区
buffer.clear();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
LocalDateTime end = LocalDateTime.now();
Duration between = Duration.between(start, end);
System.out.println(between.getSeconds() + "秒");
}
/**
* 通道传输 + 直接缓冲区
* 我的情况:
* 一运行程序,文件夹中的视频就已经复制好了
* 但是程序需要JVM去垃圾收集掉直接缓冲区,程序才会结束 所以需要很久
*
* 107秒 无System.gc();
* 9秒 有System.gc();
* System.gc() 不一定会去垃圾回收
*/
@Test
public void test2() {
LocalDateTime start = LocalDateTime.now();
try (
FileChannel inChannel = FileChannel.open(
//Path path:地址 OpenOption... options:指定打开文件的方式
Paths.get("D:\\QLDownload\\熊出没·奇幻空间\\熊出没·奇幻空间 熊出没奇幻空间 蓝光(1080P).qlv"),
//只读
StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(
Paths.get("D:\\QLDownload\\熊出没·奇幻空间\\2.qlv"),
//CREATE_NEW:如果文件存在就报错,不存在就创建(安全)
//CREATE:存在就覆盖
StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE_NEW)) {
//通过map(模式,当前位置,总量)方法创建 映射字节缓冲区
//MappedByteBuffer是DirectByteBuffer的父类 都可以通过这个引用操作那块物理内存
MappedByteBuffer inMappedBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMappedBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
byte[] bytes = new byte[inMappedBuffer.limit()];
//读源文件管道的直接缓冲区的数据写到bytes中
inMappedBuffer.get(bytes);
//读bytes中的数据写到目标文件管道的直接缓冲区中
outMappedBuffer.put(bytes);
} catch (IOException e) {
e.printStackTrace();
}
System.gc();
LocalDateTime end = LocalDateTime.now();
Duration between = Duration.between(start, end);
System.out.println(between.getSeconds() + "秒");
}
transferFrom,transferTo 通道运输
还有transferFrom()
和transferTo()
方法直接通道传输,底层帮我们写直接缓冲区
/**
* 通道传输
* 使用transferFrom和transferTo直接通道传输,底层帮我们写直接缓冲区
* 12s
*/
@Test
public void test3() {
LocalDateTime start = LocalDateTime.now();
try (
FileChannel inChannel = FileChannel.open(
//Path path:地址 OpenOption... options:指定打开文件的方式
Paths.get("D:\\QLDownload\\熊出没·奇幻空间\\熊出没·奇幻空间 熊出没奇幻空间 蓝光(1080P).qlv"),
//只读
StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(
Paths.get("D:\\QLDownload\\熊出没·奇幻空间\\2.qlv"),
//CREATE_NEW:如果文件存在就报错,不存在就创建(安全)
//CREATE:存在就覆盖
StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE_NEW)) {
//inChannel.transferTo(long position, long count,WritableByteChannel target):
// 将inChannel管道中position位置,count长度的字节数据 传送到 目标可写字节管道中
//inChannel.transferTo(0, inChannel.size(), outChannel);
//outChannel.transferFrom(ReadableByteChannel src,long position, long count)
//从可读字节管道中position位置,count长度的字节数据 传送到 此管道中
//盲猜 方法内部自己使用了直接缓冲区
outChannel.transferFrom(inChannel,0,inChannel.size());
} catch (IOException e) {
e.printStackTrace();
}
LocalDateTime end = LocalDateTime.now();
System.out.println(Duration.between(start, end).getSeconds() + "s");
}
分散(Scatter)与聚集(Gather)
分散读取: 一个通道中的数据分散读取到成多个缓冲区中(按照顺序)
聚集写入: 把多个缓冲区的数据聚集写入到一个通道中 (按照顺序)
实际就是用通道的read(),wirte()的重载方法,放进去一个缓冲区数组
public final long read(ByteBuffer[] dsts) throws IOException {
return read(dsts, 0, dsts.length);
}
public final long write(ByteBuffer[] srcs) throws IOException {
return write(srcs, 0, srcs.length);
}
如果是图片的话,不太建议这种方式,只有当第一个缓冲区比图片大时才能运输成功图片,否则打开的不是原来的图片了
@Test
public void test() {
try (
//RandomAccessFile(String name文件位置, String mode模式:读,读写等)
RandomAccessFile accessInFile = new RandomAccessFile("D:\\Tc.l\\学习\\依赖.txt", "rw");
FileChannel channel1 = accessInFile.getChannel();
RandomAccessFile accessOutFile = new RandomAccessFile("D:\\Tc.l\\学习\\1.txt", "rw");
FileChannel channel2 = accessOutFile.getChannel();
) {
ByteBuffer buffer1 = ByteBuffer.allocate(100);
ByteBuffer buffer2 = ByteBuffer.allocate(1024);
ByteBuffer[] buffers = {buffer1,buffer2};
//把通道1中的数据 写到多个缓冲区中(按顺序,写满了就换下一个)
channel1.read(buffers);
//打印多个缓冲区中的数据
for (ByteBuffer buffer : buffers) {
System.out.println(new String(buffer.array(),0,buffer.limit()));
}
//把每一个缓冲区都切换成读模式
for (ByteBuffer buffer : buffers) {
buffer.flip();
}
//把多个缓冲区中的数据 写到 通道2中
channel2.write(buffers);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
字符集
在java.nio.charset.Charset包中
可以拿到编码器,解码器,可以对缓冲区进行编码与解码
@Test
public void printAvailableCharsets() {
//打印所有可用字符集
SortedMap<String, Charset> map = Charset.availableCharsets();
Set<Map.Entry<String, Charset>> entrySet = map.entrySet();
for (Map.Entry<String, Charset> entry : entrySet) {
System.out.println(entry.getKey() + "===" + entry.getValue());
}
}
@Test
public void test() throws CharacterCodingException {
Charset charset = Charset.forName("UTF-8");
//得到UTF-8编码器
CharsetEncoder charsetEncoder = charset.newEncoder();
//得到UTF-8解码器
CharsetDecoder charsetDecoder = charset.newDecoder();
CharBuffer buffer = CharBuffer.allocate(1024);
buffer.put("采用UTF-8编码");
//切换读模式
buffer.flip();
//使用UTF-8编码器进行编码 得到字节缓冲区
ByteBuffer encodeBuffer = charsetEncoder.encode(buffer);
for (int i = 0; i < 17; i++) {
System.out.println(encodeBuffer.get());
}
//使用UTF-8解码器对刚刚编的码 进行 解码(事先需要把刚刚编的码切换模式)
encodeBuffer.flip();
CharBuffer decodeBuffer = charsetDecoder.decode(encodeBuffer);
System.out.println(decodeBuffer.toString());
System.out.println("==================================================");
//使用GBK解码器对刚刚编的码 进行 解码(发生乱码)
Charset gbk = Charset.forName("GBK");
CharsetDecoder gbkDecoder = gbk.newDecoder();
encodeBuffer.flip();
CharBuffer gbkDecodeBuffer = gbkDecoder.decode(encodeBuffer);
System.out.println(gbkDecodeBuffer.toString());
/*
采用UTF-8编码
==================================================
閲囩敤UTF-8缂栫爜
*/
}
阻塞与非阻塞
阻塞
传统IO是阻塞式的,某线程调用读写操作时,会被阻塞直到有数据被读取或写入,在被阻塞期间线程不能执行其他任务
因此在网络通信时,服务端要为每一个客户端提供一个线程来处理读写,当客户端数量多时,性能会下降
模拟阻塞式网络通信
@Test
public void client() {
try (
//1.获得通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
FileChannel inFileChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ)) {
//2.指定缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//3.读取本地文件,发送到服务器
while ((inFileChannel.read(buffer)) != -1) {
buffer.flip();
sChannel.write(buffer);
buffer.clear();
}
//相当于告诉服务端输入结束了
sChannel.shutdownOutput();
//接受服务器端的反馈
int len = 0;
while ((len = sChannel.read(buffer))!=-1){
buffer.flip();
System.out.println(new String(buffer.array(),0,len));
buffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void server() {
try (
//1.获得通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
FileChannel outFileChannel = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
//2.绑定连接
ssChannel.bind(new InetSocketAddress(9898));
//3.指定缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//4.拿到客户端通道
SocketChannel sChannel = ssChannel.accept();
//5.接受客户端数据,保存到本地
while ((sChannel.read(buffer)) != -1) {
buffer.flip();
outFileChannel.write(buffer);
buffer.clear();
}
//6.给客户端响应
buffer.put("服务端接受数据成功".getBytes());
buffer.flip();
sChannel.write(buffer);
//7.关闭客户端通道
sChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
非阻塞
Java NIO是非阻塞的,某线程在某通道调用读写操作时,若没有数据被读取或写入,该线程就会执行其他任务(线程通常把非阻塞IO操作的空间时间用在其他通道上执行IO操作)
因此在网络通信时,NIO可以让服务器用一个或多个线程来处理连接服务器端的所有客户端
选择器(Selector)
前面说过选择器是NIO实现非阻塞式的核心
-
把客户端通道注册到选择器中
-
选择器监控这些客户端通道的状况
-
只有某个通道"准备就绪"了,选择器才把它发送到服务端的一个或多个线程上
Selector
是SelectableChannel
对象的多路复用器
Selector
可以同时监控多个SelectableChannel
对象(利用Selector可以让一个线程管理多个通道)
-
SelectableChannel
结构
-
使用
Selector.open()
方法获得选择器Selector selector = Selector.open();
-
使用
SelectableChannel.register(Selector sel,int ops)
方法来将通道注册到此选择器上//获得通道 ServerSocketChannel ssChannel = ServerSocketChannel.open() //切换为非阻塞模式 ssChannel.configureBlocking(false); //register(Selector sel[注册到哪个选择器上], int ops[监听事件类型]) ssChannel.register(selector, SelectionKey.OP_ACCEPT);
-
-
SelectionKey
表示SelectableChannel
和Selector
之间的注册关系- 监听事件类型可用
SelectionKey
的四个常量表示
SelectionKey.OP_READ = 1 << 0
读SelectionKey.OP_WRITE = 1 << 2
写SelectionKey.OP_CONNECT = 1 << 3
连接SelectionKey.OP_ACCEPT = 1 << 4
接收
如果注册时不止一个监听事件可以用逻辑位或
|
连接 - 监听事件类型可用
SocketChannel
SocketChannel是一个连接到TCP网络套接字的通道
模拟非阻塞式网络通信
@Test
public void client() {
try (//1.得到通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9890))) {
//2.切换成非阻塞式
sChannel.configureBlocking(false);
//3.创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//4.发送文件
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String s = now.format(formatter);
buffer.put(s.getBytes());
buffer.flip();
sChannel.write(buffer);
buffer.clear();
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void server() {
try ( //1.获得通道
ServerSocketChannel ssChannel = ServerSocketChannel.open()) {
//2.切换成非阻塞式
ssChannel.configureBlocking(false);
//3.绑定连接
ssChannel.bind(new InetSocketAddress(9890));
//4.获取选择器
Selector selector = Selector.open();
//5.把通道注册到选择器上,并指定监听事件
//register(Selector sel[注册到哪个选择器上], int ops[SelectionKey属性常数])
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
//6.轮询式获取选择器上已经准备就绪的事件
while (selector.select() > 0) {
//7.获得当前选择器中所有注册的 选择键(就绪的监听事件)
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
//8.获取准备就绪的事件
SelectionKey key = iterator.next();
//9.判断具体是什么事件准备就绪
if (key.isAcceptable()) {
//10.接受就绪事件 获得客户端
SocketChannel sChannel = ssChannel.accept();
//11.切换非阻塞式
sChannel.configureBlocking(false);
//12.把该通道注册到选择器上
sChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
//13. 获得当前选择器上"读就绪"的通道
SocketChannel sChannel = (SocketChannel) key.channel();
//14.读取数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = 0;
while ((len = sChannel.read(buffer)) != -1) {
buffer.flip();
System.out.println(new String(buffer.array(), 0, len));
buffer.clear();
}
}
//15.取消选择键
iterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
DetagramChannel
DatagramChannel是一个能收发UDP包的通道
@Test
public void client() {
try (
DatagramChannel dc = DatagramChannel.open()) {
dc.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(1024);
LocalDateTime now = LocalDateTime.now();
buffer.put(now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")).getBytes());
buffer.flip();
dc.send(buffer, new InetSocketAddress("127.0.0.1", 8090));
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void server() {
try (DatagramChannel dc = DatagramChannel.open()) {
dc.configureBlocking(false);
Selector selector = Selector.open();
dc.bind(new InetSocketAddress(8090));
dc.register(selector, SelectionKey.OP_READ);
while(selector.select()>0){
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
if (key.isReadable()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
dc.receive(buffer);
buffer.flip();
System.out.println(new String(buffer.array(),0,buffer.limit()));
buffer.clear();
}
}
iterator.remove();
}
} catch (IOException e) {
e.printStackTrace();
}
}
Pipe
Pipe是管道,用于2个线程间单向数据连接
从Pipe的sink通道写入,从source通道读取
@Test
public void test1(){
try {
Pipe pipe = Pipe.open();
ByteBuffer buffer = ByteBuffer.allocate(1024);
//发送数据
Pipe.SinkChannel sinkChannel = pipe.sink();
buffer.put(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH-mm-ss")).getBytes());
buffer.flip();
sinkChannel.write(buffer);
//接受数据
Pipe.SourceChannel sourceChannel = pipe.source();
ByteBuffer allocate = ByteBuffer.allocate(1024);
int len = sourceChannel.read(allocate);
allocate.flip();
System.out.println(new String(allocate.array(),0,len));
//关闭
sinkChannel.close();
sourceChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
学习视频:尚硅谷NIO
如有错误,麻烦指出,感谢~