下面我们通过代码来看一下Channel和Selector相关的功能和用法。
首先通过我们平常最为了解的文件流出发。文件流中对应的就是文件通道,代码如下:
package stream.nio; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; /** * @Description: channel测试 * @Author: haoqiangwang3 * @CreateDate: 2020/1/10 */ public class ChannelTest { public static void main(String[] args) throws IOException { fileChannelTest(); } /** * 文件通道 * @throws IOException */ public static void fileChannelTest() throws IOException { // 设置输入源 & 输出地 = 文件 String infile = "D:\Work\Code\MyProject\spring-boot-example\example-test\src\main\java\stream\nio\rsa_private.key"; String outfile = "D:\Work\Code\MyProject\spring-boot-example\example-test\src\main\java\stream\nio\rsa_private_cp.key"; // 1.获取数据源 和 目标传输地的输入输出流(此处以数据源 = 文件为例) FileInputStream fin = new FileInputStream(infile); FileOutputStream fout = new FileOutputStream(outfile); // 2.获取数据源的输入输出通道 FileChannel finChannel = fin.getChannel(); FileChannel foutChannel = fout.getChannel(); // 3.创建缓冲区对象:Buffer ByteBuffer buff = ByteBuffer.allocate(1024); // 4.从通道读取数据 & 写入到缓冲区 // 注:若已经读取到该通道数据的末尾,则返回-1 while (finChannel.read(buff) != -1){ //5.传出数据准备:将缓存区的写模式 转换为 读模式 buff.flip(); //6.从Buffer中读取数据到通道 foutChannel.write(buff); //7.清除缓冲区 // 注意:使用完后一定要清除 buff.clear(); } } }
此程序通过文件通道实现了文件的复制功能。从代码中也可发现文件内容的读写都是用一个Buffer实现的。但是需要读写模式转换。此方法用了两个Channel是为了先方便对此有个概念,下面我们来看用一个通道对文件进行读写操作。
第一种方法:
public static void fileChannelTest2() throws IOException { // 操作文件地址 String filePath = "D:\Work\Code\MyProject\spring-boot-example\example-test\src\main\java\stream\nio\rsa_private.key"; // 获取文件通道,注意后面的操作类型的选择 FileChannel fileChannel = FileChannel.open(new File(filePath).toPath(), StandardOpenOption.READ,StandardOpenOption.WRITE); // 创建缓冲区对象:Buffer ByteBuffer buff = ByteBuffer.allocate(512); int len = 0; // 从通道读取数据 while ((len = fileChannel.read(buff)) != -1){ //读取buffer中的内容 System.out.println(new String(buff.array(),0,len)); //清除缓冲区 buff.clear(); } String str = "写进文件中"; buff.put(str.getBytes()); //转换模式,读取buff中的数据写到文件中 buff.flip(); //将数据写到文件中 fileChannel.write(buff); buff.clear(); fileChannel.close(); }
此方法不是很常见,但是也能实现效果。下面看第二种方法:
/** * 此种方法是先写再读文件,但是将写到文件最前面。如果想写到文件最后面,则需要将文件指针指向最后面, * 读文件的时候,文件指针就会移动,所以将文件内容读取完再进行写操作,就可以实现追加的效果 */ public static void fileChannelTest3(){ RandomAccessFile aFile = null; try{ aFile = new RandomAccessFile("D:\Work\Code\MyProject\spring-boot-example\example-test\src\main\java\stream\nio\rsa_private.key","rw"); //得到文件通道 FileChannel fileChannel = aFile.getChannel(); //创建一个缓冲区 ByteBuffer buff = ByteBuffer.allocate(1024); String str = "RandomAccessFile写进文件中"; //向buff中写入数据 buff.put(str.getBytes()); //转换模式,将buff中的数据写到文件中 buff.flip(); //将buff中的数据写到文件中 fileChannel.write(buff); //清除缓冲区 buff.clear(); int len = 0; while((len = fileChannel.read(buff)) != -1) { //读取buffer中的内容 System.out.println(new String(buff.array(),0,len)); //清除缓冲区 buff.clear(); } fileChannel.close(); }catch (IOException e){ e.printStackTrace(); }finally{ try{ if(aFile != null){ aFile.close(); } }catch (IOException e){ e.printStackTrace(); } } }
由以上代码可以发现,FileChannel可以通过RandomAccessFile、FileInputStream、 FileOutputStream来获取。
通道就介绍到这里,下面Selector将和SocketChannel一起介绍,最常用的就是我们之前写过的socket连接的升级版,用NIO来实现socket通讯的服务端。
代码如下:
package stream.nio; 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.util.Iterator; import java.util.Set; public class SelectorTest { public static void main(String[] args) throws IOException, InterruptedException { //创建serverSocketChannel,监听8081端口 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1",8081)); //设置为非阻塞模式 serverSocketChannel.configureBlocking(false); //为serverSocketChannel注册selector Selector selector = Selector.open(); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); System.out.println("NIO socket Server 启动..."); while(true){ //通过此进行阻塞 selector.select(); System.out.println("开始处理请求..."); Set<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> it = keys.iterator(); while(it.hasNext()){ //获取需要处理的SelectionKey SelectionKey key = it.next(); //进行处理 handler(key); //获取的处理事件需要移除 it.remove(); } } } /** * 根据key的状态进行不同的操作 * @param key * @throws IOException * @throws InterruptedException */ public static void handler(SelectionKey key) throws IOException, InterruptedException { if(key.isAcceptable()){ System.out.println("此请求为可接受状态..."); //获取socketChannel SocketChannel socketChannel = ((ServerSocketChannel)key.channel()).accept(); //设置为非阻塞 socketChannel.configureBlocking(false); //注册关心的selector,可读的事件 socketChannel.register(key.selector(),SelectionKey.OP_READ); System.out.println("建立了连接请求..."); } if(key.isReadable()){ System.out.println("此请求为可读状态..."); Thread.sleep(10000); SocketChannel socketChannel = (SocketChannel)key.channel(); //创建缓冲区 ByteBuffer buffer = ByteBuffer.allocate(512); //读取客户端发送的内容 int len = 0; while ((len = socketChannel.read(buffer)) != -1){ System.out.println("接收到的内容为:" + new String(buffer.array(),0,len)); //清除缓冲区 buffer.clear(); } //返回客户端信息 String respStr = "hello world"; buffer.put(respStr.getBytes()); //转换模式 buffer.flip(); socketChannel.write(buffer); buffer.clear(); //建立的连接需要关闭 socketChannel.close(); } } }
运行此服务端,可以通过之前socket中的额客户端进行通讯。我们可以发现他是通过 selector.select()方法来进行阻塞,通过不断轮训注册在他上面的通道,发现他感兴趣的操作后进行对应的处理操作。