• java NIO学习(二)


    下面我们通过代码来看一下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()方法来进行阻塞,通过不断轮训注册在他上面的通道,发现他感兴趣的操作后进行对应的处理操作。

  • 相关阅读:
    使用RPC的接口创建账户同时购买内存并为其抵押CPU和NET资源
    使用RPC的接口创建账户
    【移动安全基础篇】——21、Android脱壳思路
    插件
    NGUI 优化
    影子
    优化文章索引
    MVC
    《你不常用的c#之XX》
    CMake
  • 原文地址:https://www.cnblogs.com/wanghq1994/p/12176973.html
Copyright © 2020-2023  润新知