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

posted @ 2020-01-10 16:51  wanghq1994  阅读(148)  评论(0编辑  收藏  举报