本笔记记录了视频Java NIO 视频教程全集的学习笔记。
1. Java NIO 简介
Java NIO( New IO) 是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同, NIO支持面向缓冲区的、基于通道的IO操作。 NIO将以更加高效的方式进行文件的读写操作。
2. Java NIO 与 IO 的主要区别
3. 缓冲区(Buffer)和通道(Channel)
Java NIO系统的核心在于:通道(Channel)和缓冲区(Buffer)。通道表示打开到 IO 设备(例如:文件、套接字)的连接。若需要使用 NIO 系统,需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。
简而言之,Channel 负责传输, Buffer 负责存储。
缓冲区(Buffer)
-
缓冲区(Buffer):一个用于特定基本数据类型的容器。由 java.nio 包定义的,所有缓冲区都是 Buffer 抽象类的子类。
-
Java NIO 中的 Buffer 主要用于与 NIO 通道进行交互,数据是从通道读入缓冲区,从缓冲区写入通道中的。
-
Buffer 就像一个数组,可以保存多个相同类型的数据。根据数据类型不同( boolean 除外),有以下 Buffer 常用子类:
- ByteBuffer
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
上述 Buffer 类 他们都采用相似的方法进行管理数据,只是鸽子管理的数据类型不同而已。都是通过
static XxxBuffer allocate(int capacity)
获取一个 Buffer 对象 -
Buffer 中的重要概念
- 容量 (capacity):表示 Buffer 最大数据容量,缓冲区容量不能为负,并且创建后不能更改。
- 限制(limit):第一个不应该读取或写入的数据的索引,即位于 limit 后的数据不可读写。缓冲区的限制不能为负,并且不能大于其容量。
- 位置(position):下一个要读取或写入的数据的索引。缓冲区的位置不能为负,并且不能大于其限制。
- 标记(mark)与重置(reset):标记是一个索引,通过 Buffer 中的 mark() 方法指定 Buffer 中一个特定的 position,之后可以通过调用 rest()方法恢复到这个 position。
- 0 <= mark <= position <= limit <= capacity
缓冲区的基本属性
缓冲区的操作
直接上代码:
package top.db.nio.p2;
import java.nio.ByteBuffer;
/**
*/
public class ByteTest {
// 缓冲区 Buffer, Java NIO中负责数据的存取。用于存取不同数据类型
// ByteBuffer
// CharBuffer
// ShortBuffer
// IntBuffer
// LongBuffer
// FloatBuffer
// DoubleBuffer
// 上述缓存区的管理方式几乎一致,通过allocate()获取缓冲区
// 二、缓冲区存取数据的两个核心方法
// put():存入缓冲区数据
// get():获取缓冲区中的数据
//
// 三、缓冲区的4个核心指标属性
// capacity:容量,表示缓冲区中最大存储数据的容量,一旦声明不能改变
// limit:界限,表示缓冲区中路操作数据的大小,(limit后数据不能读写)
// position:位置,表示缓存区正在操作数据的位置
//
//
// position <=limit <=capacity
//
//
/**
* 我们通过一个比较常用的ByteBuffer来熟悉一下
* allocate(int capacity)
* put(byte[] bytes)
* flip()
* get(byte[] bytes, int offset, int length)
* 我们通过以下代码来观察以上四种方法调用的时候,
* position
* limit
* capacity
* 的变化
*
* @param args
*/
public static void main(String[] args) {
ByteBuffer buf = ByteBuffer.allocate(1024);
System.out.println("-------------allocate-----------------");
System.out.println("capacity:" + buf.capacity());
System.out.println("limit:" + buf.limit());
System.out.println("position:" + buf.position());
String s = "hello";
buf.put(s.getBytes());
System.out.println("-------------put-----------------");
System.out.println("capacity:" + buf.capacity());
System.out.println("limit:" + buf.limit());
System.out.println("position:" + buf.position());
buf.flip();
System.out.println("-------------flip-----------------");
System.out.println("capacity:" + buf.capacity());
System.out.println("limit:" + buf.limit());
System.out.println("position:" + buf.position());
byte[] bytes = new byte[1024];
buf.get(bytes, 0, buf.limit());
System.out.println("-------------get-----------------");
System.out.println("capacity:" + buf.capacity());
System.out.println("limit:" + buf.limit());
System.out.println("position:" + buf.position());
System.out.println(new String(bytes, 0, buf.limit()));
}
}
直接与非直接缓冲区
- 字节缓冲区要么是直接的,要么是非直接的。如果为直接字节缓冲区,则 Java 虚拟机会尽最大努力直接在
此缓冲区上执行本机 I/O 操作。也就是说,在每次调用基础操作系统的一个本机 I/O 操作之前(或之后),
虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区中(或从中间缓冲区中复制内容)。 - 直接字节缓冲区可以通过调用此类的 allocateDirect() 工厂方法来创建。此方法返回的缓冲区进行分配和取消
分配所需成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此,它们对
应用程序的内存需求量造成的影响可能并不明显。所以,建议将直接缓冲区主要分配给那些易受基础系统的
本机 I/O 操作影响的大型、持久的缓冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好
处时分配它们。 - 直接字节缓冲区还可以通过 FileChannel 的 map() 方法 将文件区域直接映射到内存中来创建。该方法返回
MappedByteBuffer 。 Java 平台的实现有助于通过 JNI 从本机代码创建直接字节缓冲区。如果以上这些缓冲区
中的某个缓冲区实例指的是不可访问的内存区域,则试图访问该区域不会更改该缓冲区的内容,并且将会在
访问期间或稍后的某个时间导致抛出不确定的异常。 - 字节缓冲区是直接缓冲区还是非直接缓冲区可通过调用其 isDirect() 方法来确定。提供此方法是为了能够在
性能关键型代码中执行显式缓冲区管理。
通道
通道(Channel):由 java.nio.channels
包定义的。Channel 表示 IO 源与目标打开的连接。 Channel 类似于传统的“流”。只不过 Channel 本身不能直接访问数据, Channel 只能与 Buffer 进行交互。
- Java 为 Channel 接口提供的最住哟啊实现类如下:
- FileChannel:用于读取、写入、映射和操作文件的通道。
- DatagramChannel:通过 UDP 读写网络中的数据通道 。
- SocketChannel:通过 TCP 读写网络中的数据。
- ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来
的连接都会创建一个 SocketChannel。
- 获取通道的一种方式是对支持通道的对象调用getChannel() 方法。支持通道的类如下:
- FileInputStream
- FileOutputStream
- RandomAccessFile
- DatagramSocket
- Socket
- ServerSocket
- 获取通道的其他方式是使用 Files 类的静态方法 newByteChannel() 获
取字节通道。或者通过通道的静态方法 open() 打开并返回指定通道。
通道的代码如下:
package top.db.nio.p3;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ResourceUtils;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
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;
/**
* 测试
*
*/
@Slf4j
public class ChannelTest {
/*
一、通道(Channel):
用于源节点与目标节点的连接,
在Java NIO中负责数据的传输,
Channel本省不存储数据,
因此需要配合缓冲区进行传输
二、通道的主要实现类
java.nio.channels.Channel 接口 since 1.4
java.nio.channels.ByteChannel
java.nio.channels.FileChannel
|--SocketChannel
|--ServerSocketChannel
|--DatagramChannel
三、获取通道
1.Java针对通道的类提供了getChannel()方法
本地IO:
FileInputStream、FileOutStream
RandomAccessFile
网络IO:
Socket
ServerSocket
DatagramSocket
2.在JDK1.7==NIO2 中 静态方法: open()
3.在JDK1.7==NIO2 中 Files工具类中newByteChannel()
四、通道之间传输数据
transferTo()
transferFrom()
五、分散(Scatter)与聚集(Gather)
分散读取: 将通道中大数据分散到多个缓冲区中
聚集写入:将多个缓冲区中的数据聚集到通道中
注意:按照缓冲区的顺序,从channel中读取依次将buffer填满
六、字符集:CharSet
编码:字符串-->字节数组
解码:字节数组-->字符串
*/
public static void main(String[] args) throws IOException {
// new ChannelTest().test1();
// new ChannelTest().test2();
// new ChannelTest().test3();
// new ChannelTest().test4();
// new ChannelTest().test5();
}
/**
* 字符集
*
* @throws CharacterCodingException
*/
private void test5() throws CharacterCodingException {
// 下面三行代码用来查看NIO包下的Charset都有什么字符集
// SortedMap<String, Charset> map = Charset.availableCharsets();
// for (Map.Entry<String, Charset> entry : map.entrySet()) {
// System.out.println(entry.getKey() + "--" + entry.getValue());
// }
Charset gbkCharset = Charset.forName("GBK");
//获取编码器
CharsetEncoder charsetEncoder = gbkCharset.newEncoder();
//获取解码器
CharsetDecoder charsetDecoder = gbkCharset.newDecoder();
// 分配缓冲空间
CharBuffer cBuffer = CharBuffer.allocate(1024);
String msg = "测试测试1234";
// 将数据写入缓冲区
cBuffer.put(msg);
// 转换成写模式
cBuffer.flip();
// 编码,使用GBK编码器对流进行编码
ByteBuffer bBuffer = charsetEncoder.encode(cBuffer);
// 可以获得编码后的结果
for (int i = 0; i < msg.length(); i++) {
// 这里的原理是,每get一次,position都会移动
System.out.println(bBuffer.get());
}
// 解码,转换成写模式
bBuffer.flip();
// 使用GBK解码
CharBuffer decodeCharBuffer = charsetDecoder.decode(bBuffer);
System.out.println(decodeCharBuffer.toString());
System.out.println("__________________________________");
// 使用UTF-8进行解码
Charset charset2 = Charset.forName("UTF-8");
bBuffer.flip();
CharBuffer decode2 = charset2.decode(bBuffer);
// 编码解码使用的字符集不同,必然是乱码的结果
System.out.println(decode2.toString());
}
/**
* 分散(Scatter)与聚集(Gather)
* 老师在这里用到了一个类 RandomAccessFile, 这里用这个类我感觉不太方便我们理解
* 我这边将 RandomAccessFile 改成我们学习阶段比较舒徐的流的方式,方便大家理解 Scatter 与 Gather
* @throws IOException
*/
private void test4() throws IOException {
File file = ResourceUtils.getFile("222.jpg");
try ( // 1.获取通道
FileInputStream fileInputStream = new FileInputStream(file);
FileOutputStream fileOutputStream = new FileOutputStream("333.jpg");
FileChannel inChannel = fileInputStream.getChannel();
FileChannel outChannel = fileOutputStream.getChannel()
) {
// 2.分配指定大小的缓冲区
ByteBuffer buffer1 = ByteBuffer.allocate(1024);
ByteBuffer buffer2 = ByteBuffer.allocate(4028000);
// 3.分散获取
ByteBuffer[] buffers = {buffer1, buffer2};
long read = inChannel.read(buffers);
System.out.println(read);
for (ByteBuffer buffer : buffers) {
buffer.flip();
}
// 4.聚集写入
System.out.println(outChannel.write(buffers));
for (ByteBuffer buffer : buffers) {
buffer.clear();
}
}
}
/**
* transferTo
* transferFrom
*/
private void test3() {
try (// 开启通道
FileChannel inChannel = FileChannel.open(Paths.get("222.jpg"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("333.jpg"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
) {
// // 使用transferTo
// inChannel.transferTo(0, inChannel.size(), outChannel);
// 使用transferFrom
outChannel.transferFrom(inChannel, 0, inChannel.size());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 2.利用通道完成文件复制(直接缓冲区--内存映射)
* 通过FileChannel类的open()方法直接获取文件通道
* 声明内存映射(直接缓冲)
* 文件传输
*
*/
private void test2() {
try ( // 1.创建通道
FileChannel inChannel = FileChannel.open(Paths.get("222.jpg"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("333.jpg"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE)
) {
// 2.声明内存映射 ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
MappedByteBuffer inMappedByteBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMappedByteBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
// 3.文件传输 -- 直接对缓冲区数据进行读写操作
byte[] dest = new byte[inMappedByteBuffer.limit()];
inMappedByteBuffer.get(dest);
outMappedByteBuffer.put(dest);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 本机内文件移动
* 1、利用通道完成文件复制(非直接缓冲区--JVM 堆内存)
* 通过FileInputStream获取系统中的文件,将其转为流
* 分配缓冲区,使用ByteBuffer.allocate(1024)
* 通过FileInputStream获取通道,使用通道来读取文件
* 边读边写,ByteBuffer既可以写入数据,也可以读取数据
* 因此直接使用FileOutputStream获取输出通道
* 通过输出通道写出数据即可
*
* @throws FileNotFoundException
*/
public void test1() throws FileNotFoundException {
File file = ResourceUtils.getFile("D:/壁纸/看电脑的女生.jpg");
String target = "222.jpg";
try ( // 获取流
FileInputStream fileInputStream = new FileInputStream(file);
FileOutputStream fileOutputStream = new FileOutputStream(target);
// 获取通道
FileChannel fileInputChannel = fileInputStream.getChannel();
FileChannel fileOutputStreamChannel = fileOutputStream.getChannel()) {
// 分配指定缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 将通道中的数据存入缓冲区
// 实际上就是,缓冲区每读满一次,写入一次,直到读完所有数据
while (fileInputChannel.read(buffer) != -1) {
// 通过虾米那四行代码可以清晰的看到拷贝的数据流动
System.out.println("------------------------------");
System.out.println("capacity:" + buffer.capacity());
System.out.println("limit:" + buffer.limit());
System.out.println("position:" + buffer.position());
// 将缓冲区(buffer)切换成读数据模式
buffer.flip();
// 将缓冲区(buffer)的数据写入通道
fileOutputStreamChannel.write(buffer);
// 清空缓冲区
buffer.clear();
}
} catch (IOException e) {
log.error("IOException occupy", e);
}
}
}
4. NIO 的非阻塞式网络通信
直接上代码:
阻塞网络通信
package top.db.nio.p9;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
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;
@Slf4j
public class ServerSocketChannelTest {
//客户端数据发送一个读写请求到服务端
// 服务无端不能确定客户端判断数据真实有效状态时候,该线程会一直处于阻塞状态,服务端会等客户端发送
//
// 服务端会判断数据在内核地址空间中是否存在,此时服务端线程阻塞,原来是多线程来解决,为每一个请求分配一个线程来处理请求,充分利用cpu资源,
// 后续请求,线程没有100%的利用,
//
// NIIO--非阻塞式: 通道加缓冲区,
// 把每一个用于传输数据的通道注册到选择器,选择器实时监控通道上的状况,
// 当某一个通道上的某一个请求的事件完全准备就绪时,选择器才会将这个任务分配到服务端的一个或多个线程上在去运行
//
//
//
//
// 读数据状态--完全准备就绪
// 选择器:Selector
//
//===============================
// 一、使用NIO完成网络通信的三个核心
//1、通道(Channel::负责连接
// java.nio.channels.Channel
// |--SelectableChannel
// |--ServerSocketChannel
// |--DatagramChannel
//
// |--Pipe.SinkChannel
// |--Pipe.SourceChannel
//
// FileChannel不能切换成非阻塞模式
//
//
//2、缓冲区(Buffer):负责数据的存取
//
//3、选择器(Selector):是SelectableChannel的多路复涌去。用于监控SelectableChannel的IO情况
//
//
public static void main(String[] args) throws InterruptedException {
long s1 = System.currentTimeMillis();
Runnable server = new Runnable() {
@Override
public void run() {
new ServerSocketChannelTest().server();
}
};
Runnable client = new Runnable() {
@Override
public void run() {
new ServerSocketChannelTest().client();
}
};
//todo 为何两个线程启动 不是串行的
server.run();
Thread.sleep(100L);
client.run();
System.out.println("END=" + (System.currentTimeMillis() - s1));
}
//客户端
@Test
public void client() {
//1、获取通道
SocketChannel socketChannel = null;
FileChannel inChannel = null;
try {
socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
//测试中是发送一个图片给server,这里用FileChannel去获取
inChannel = FileChannel.open(Paths.get("T:/data/nio/1.jpg"), StandardOpenOption.READ);
//2、分配指定的大小的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//3、发送数据
//long transferTo = inChannel.transferTo(0, inChannel.size(), socketChannel);
while (inChannel.read(buffer) != -1) {
buffer.flip();
socketChannel.write(buffer);
buffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
log.error("结果={}", e);
} finally {
if (inChannel != null) {
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
log.error("结果={}", e);
}
}
if (socketChannel != null) {
try {
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
log.error("结果={}", e);
}
}
}
}
//服务器--阻塞式
@Test
public void server() {
ServerSocketChannel ssChannel = null;
SocketChannel acceptSocketChannel = null;
FileChannel outChannel = null;
try {
//1、获取通道
ssChannel = ServerSocketChannel.open();
outChannel = FileChannel.open(Paths.get("T:/data/nio/_21.jpg"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
//2、绑定连接
ssChannel.bind(new InetSocketAddress(9898));
//3、获取客户端连接的通道
acceptSocketChannel = ssChannel.accept();
//4、分配指定大小的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//5、接收客户端的数据,并保存到本地
while (acceptSocketChannel.read(buffer) != -1) {
buffer.flip();
outChannel.write(buffer);
buffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
log.error("结果={}", e);
} finally {
if (ssChannel != null) {
try {
ssChannel.close();
} catch (IOException e) {
e.printStackTrace();
log.error("结果={}", e);
}
}
//acceptSocketChannel
if (acceptSocketChannel != null) {
try {
acceptSocketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//outChannel
if (outChannel != null) {
try {
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
log.error("结果={}", e);
}
}
}
}
}
非阻塞通信1
package top.db.nio.p10;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.time.LocalDateTime;
import java.util.Iterator;
import java.util.Scanner;
@Slf4j
public class NoBlockingNioTest {
// 读数据状态--完全准备就绪
// 选择器:Selector
//
//===============================
// 一、使用NIO完成网络通信的三个核心
//1、通道(Channel::负责连接
// java.nio.channels.Channel
// |--SelectableChannel
// |--ServerSocketChannel
// |--DatagramChannel
//
// |--Pipe.SinkChannel
// |--Pipe.SourceChannel
//
// FileChannel不能切换成非阻塞模式
//
//
//2、缓冲区(Buffer):负责数据的存取
//
//3、选择器(Selector):是SelectableChannel的多路复涌去。用于监控SelectableChannel的IO情况
//
//
//
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("请输入一个字符串(中间能加空格或符号)");
String line = input.nextLine();
String s = LocalDateTime.now().toString() + "\n" + line;
System.out.println(s);
}
//客户端
@Test
public void client() {
SocketChannel socketChannel = null;
FileChannel inChannel = null;
try {
//1、获取通道
socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
//2、切换成非阻塞模式
socketChannel.configureBlocking(false);
//3、分配缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//测试中是发送一个 时间字符串给server
//
String msg = getMsg();
buffer.put(msg.getBytes());
buffer.flip();
//4、发送数据
socketChannel.write(buffer);
buffer.clear();
} catch (IOException e) {
e.printStackTrace();
log.error("结果={}", e);
} finally {
if (inChannel != null) {
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
log.error("结果={}", e);
}
}
if (socketChannel != null) {
try {
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
log.error("结果={}", e);
}
}
}
}
private String getMsg() {
// Scanner scanner = new Scanner(System.in);
// System.out.println("请输入:");
// while (scanner.hasNext()) {
// String msg = scanner.next();
// }
// return "null";
// Scanner input = new Scanner(System.in);
// System.out.println("请输入一个字符串(中间能加空格或符号)");
// String line = input.nextLine();
// String s = LocalDateTime.now().toString() + "\n" + line;
// System.out.println(s);
// scanner 在junit中不能正常工作
return "1111111";
}
// System.out.println("请输入一个字符串(中间不能加空格或符号)");
// String b = input.next();
// System.out.println("请输入一个整数");
// int c;
// c = input.nextInt();
// System.out.println("请输入一个double类型的小数");
// double d = input.nextDouble();
// System.out.println("请输入一个float类型的小数");
// float f = input.nextFloat();
// System.out.println("按顺序输出abcdf的值:");
// System.out.println(a);
// System.out.println(b);
// System.out.println(c);
// System.out.println(d);
// System.out.println(f);
//服务器--阻塞式
@Test
public void server() {
ServerSocketChannel ssChannel = null;
SocketChannel acceptSocketChannel = null;
FileChannel outChannel = null;
try {
//1、获取通道
ssChannel = ServerSocketChannel.open();
//2、设置非阻塞
ssChannel.configureBlocking(false);
//3、绑定连接
ssChannel.bind(new InetSocketAddress(9898));
//4、获取选择器
Selector selector = Selector.open();
//5、将通道注册到选择器,并且指定“监听事件” ,对比之前的 accept方法=阻塞
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
//6、轮询式的获取选择器上“已经准备就绪”的事件
while (selector.select() > 0) { //至少有一个准备就绪的事件
//7、包括所有注册的“选择键(已就绪的监听事件)”
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
//8、获取准备就绪的事件
while (iterator.hasNext()) {
//9、判断具体是什么准备就绪事件
SelectionKey sk = iterator.next();
if (sk.isAcceptable()) {
//10、若"接受就绪",获取客户端连接
SocketChannel sChannel = ssChannel.accept();
//11、设置非阻塞模式
sChannel.configureBlocking(false);
//12、将该通道注册到选择器上
sChannel.register(selector, SelectionKey.OP_READ);
} else if (sk.isReadable()) {
//13、获取当前选择器上“读就绪”状态的通道
SocketChannel sChannel = (SocketChannel) sk.channel();
//14、读取数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = 0;
while ((len = sChannel.read(buffer)) > 0) {
buffer.flip();
System.out.println(new String(buffer.array(), 0, len));
buffer.clear();
}
}
//15、取消选择键
iterator.remove();
}
}
//4、分配指定大小的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//5、接收客户端的数据,并保存到本地
while (acceptSocketChannel.read(buffer) != -1) {
buffer.flip();
outChannel.write(buffer);
buffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
log.error("结果={}", e);
} finally {
if (ssChannel != null) {
try {
ssChannel.close();
} catch (IOException e) {
e.printStackTrace();
log.error("结果={}", e);
}
}
//acceptSocketChannel
if (acceptSocketChannel != null) {
try {
acceptSocketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//outChannel
if (outChannel != null) {
try {
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
log.error("结果={}", e);
}
}
}
}
}
非阻塞通信2
package top.db.nio.p11;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
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.util.Iterator;
@Slf4j
public class NoBlockingNio2Test {
//发送端
@Test
public void send() {
DatagramChannel datagramChannel = null;
try {
datagramChannel = DatagramChannel.open();
datagramChannel.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("测试1234".getBytes());
buffer.flip();
datagramChannel.send(buffer, new InetSocketAddress("127.0.0.1", 8080));
buffer.clear();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (datagramChannel != null) {
try {
datagramChannel.close();
System.out.println("发送完成关闭 DatagramChannel!");
} catch (IOException e) {
e.printStackTrace();
log.error("e={}", e);
}
}
}
}
//接受端
@Test
public void serverDatagramChannel() {
DatagramChannel serverDatagramChannel = null;
try {
serverDatagramChannel = DatagramChannel.open();
serverDatagramChannel.configureBlocking(false);
serverDatagramChannel.bind(new InetSocketAddress(8080));
Selector selector = Selector.open();
serverDatagramChannel.register(selector, SelectionKey.OP_READ);
while (selector.select() > 0) {
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey sk = iterator.next();
if (sk.isReadable()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
serverDatagramChannel.receive(buffer);
buffer.flip();
System.out.println(new String(buffer.array(), 0, buffer.limit()));
buffer.clear();
}
}
iterator.remove();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if (serverDatagramChannel != null) {
try {
serverDatagramChannel.close();
} catch (IOException e) {
e.printStackTrace();
log.error("e={}", e);
}
}
}
}
}
5. 管道(Pipe)
管道
package top.db.nio.p12;
import org.junit.Test;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;
public class PipeTest {
/*
Java NIO 管道是两个线程之间的 单项数据连接
Pipe有一个source通道和一个sink通道。
数据会被写到sink通道,从source通道读取
*/
@Test
public void test1() throws IOException {
//1、获取管道
Pipe pipe = Pipe.open();
//2、将缓冲区中的数据写入管道
ByteBuffer buffer = ByteBuffer.allocate(1024);
Pipe.SinkChannel sinkChannel = pipe.sink();
buffer.put(" 数据会被写到sink通道".getBytes());
buffer.flip();
sinkChannel.write(buffer);
//3、数据读取到缓冲区
Pipe.SourceChannel sourceChannel = pipe.source();
buffer.flip();
int len = sourceChannel.read(buffer);
System.out.println(new String (buffer.array(),0,len));
sinkChannel.close();
sourceChannel.close();
}
}