NIO学习整理

NIO是什么

Java NIO(New IO)是从Java 1.4版本开始引入的
一个新的IO API,可以替代标准的Java IO API。
NIO与原来的IO有同样的作用和目的,但是使用
的方式完全不同,NIO支持面向缓冲区的、基于
通道的IO操作。NIO将以更加高效的方式进行文
件的读写操作。

NIO和传统IO流

这里写图片描述

1. Buffer

一,缓冲区(Buffer):

在Java NIO中负责数据类型的存取,缓冲区就是数组用来存取不同类型的数组。
对应数据类型的缓冲区(除了boolean之外):
- ByteBuffer
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
这些缓冲区的方法几乎一致,通过 allocate() 获取缓冲区

二,缓冲区存取数据的两个核心方法

put():存入数据到缓冲区
get():从缓冲区得到数据

三,缓冲区中四个重要的核心属性

capacity:容量,表示缓冲区中的最大存储容量,一旦声明不可改变
* limit*:界限,表示缓冲区中可以操作的数据大小,limit之后的数据不能读写
position:位置,表示缓冲区中正在操作的数据的位置

mark:标记,表示当前记录 position 的位置,可以通过reset()方法恢复到 mark 的位置

0 <= mark <= position <= limit <= capacity

四,操作直接缓冲区与非直接缓冲区

非直接缓冲区:通过 allocate() 方法分配缓冲区,将缓冲区建立在JVM的内存中
直接缓冲区:通过* allocateDirect() *分配直接缓冲区,将缓冲区建立在物理内存中,可提高效率

示例代码:

 @Test
    public void test2(){
        //分配直接缓冲区就是物理内存创建的缓冲区
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        System.out.println(buffer.isDirect());//判断是否是直接缓冲区 true
    }

    @Test
    public void test1(){
        String str = "abcde";

        // 1. 分配一个指定大小的缓冲区空间
        ByteBuffer buf = ByteBuffer.allocate(1024);

        System.out.println("--------allocate()--------");
        System.out.println(buf.position());     //0
        System.out.println(buf.limit());        //1024
        System.out.println(buf.capacity());     //1024

        // 2. 用 put() 方法存入数据到缓冲区
        buf.put(str.getBytes());
        System.out.println("--------put()--------");
        System.out.println(buf.position());     //5
        System.out.println(buf.limit());        //1024
        System.out.println(buf.capacity());     //1024

        // 3. flip() 切换读取数据模式
        buf.flip();
        System.out.println("--------flip()--------");
        System.out.println(buf.position());     //0
        System.out.println(buf.limit());        //5
        System.out.println(buf.capacity());     //1024

        // 4.利用 get() 读取缓冲区的数据
        byte[] bytes = new byte[buf.limit()];
        buf.get(bytes);
        System.out.println("--------get()--------");
        System.out.println("读取的值:"+new String(bytes,0,bytes.length));
        System.out.println(buf.position());     //5
        System.out.println(buf.limit());        //5
        System.out.println(buf.capacity());     //1024

        // 5. rewind() :可重复读
        buf.rewind();
        System.out.println("--------rewind()--------");
        System.out.println(buf.position());     //0
        System.out.println(buf.limit());        //5
        System.out.println(buf.capacity());     //1024

        // 6. clear() :清空缓冲区,但是缓冲区中的数据依然存在,处于“被遗忘”状态
        buf.clear();
        System.out.println("--------clear()--------");
        System.out.println(buf.position());     //0
        System.out.println(buf.limit());        //1024
        System.out.println(buf.capacity());     //1024
    }

2. Channel

一,通道(Channel):

用于源节点与目标节点的链接,Channel本身不传输数据,
需要通过Buffer缓冲区来传输数据

二,通道的的主要实现类

java.nio.channels.Channel 接口:
|–FileChannel
|–SocketChannel
|–ServerSocketChannel
|–DatagramChannel

三,获取通道

  1. Java 针对支持通道的类提供了getChannel()方法
    • 1.1 本地IO
      FileInputStream/FileOutputStream
      RandomAccessFile
    • 1.2 网络IO
      Socket
      ServerSocket
      DatagramSocket
  2. 在JDK1.7 中的NIO.2针对各个通道提供了静态方法open()
  3. 在JDK1.7 中的NIO.2的Files工具类的newByteChannel()

四,通道之间的数据传输

  • transferFrom()
  • transferTo()

五,分散(Scatter)与聚集(Gather)

  • 分散读取(Scattering Reads):将通道中的数据分散到多个缓冲区中
  • 聚集写入(Gathering Writes):将多个缓冲区中的数据聚集到通道中

六,字符集:Charset

  • 编码:字符串 -> 字节数组
  • 解码:字节数组 -> 字符串

    示例代码:

 /**
     *  5. 字符集 
     */
    @Test
    public void test5() throws CharacterCodingException{

        CharBuffer cb  = CharBuffer.allocate(128);
        cb.put("hello,你好!".toCharArray());

        // 1. 创建指定格式字符集
        Charset cs = Charset.forName("GBK");

        // 2. 获取编码器
        CharsetEncoder ce = cs.newEncoder();

        // 3. 获取解码器
        CharsetDecoder cd = cs.newDecoder();

        // 4. 编码器编码为ByteBuffer
        cb.flip();
        ByteBuffer bb = ce.encode(cb);
        cb.clear();
        for(int i =0; i < bb.limit();i++){
            System.out.println(bb.get());
        }
        // 5. 解码器解码
        bb.flip();
        CharBuffer cb2 = cd.decode(bb);
        bb.clear();
        System.out.println(cb2.toString());

    }


    /**
     *  4. 分散聚集
     */
    @Test
    public void test4() throws IOException{
        // 1. 创建文件流
        FileInputStream fis = new FileInputStream("zhaopin.txt");
        FileOutputStream fos = new FileOutputStream("zhaopin2.txt");

        // 2. 得到通道
        FileChannel inChannel = fis.getChannel();
        FileChannel outChannel = fos.getChannel();

        // 3. 创建三个不同容量的缓冲区
        ByteBuffer bb1 = ByteBuffer.allocate(128);
        ByteBuffer bb2 = ByteBuffer.allocate(64);
        ByteBuffer bb3 = ByteBuffer.allocate(32);

        ByteBuffer[] bbs = new ByteBuffer[]{bb1,bb2,bb3};

        // 4. 分散读取
        inChannel.read(bbs);
        // 4.1 切换到读模式
        for(ByteBuffer bb : bbs){
            bb.flip();
            System.out.println(new String(bb.array(),0,bb.limit()));
            System.out.println("--------------------------");
        }

        // 5. 聚集写入
        outChannel.write(bbs);

        //恢复position指针
        for(ByteBuffer bb : bbs){
            bb.clear();
        }

        inChannel.close();
        outChannel.close();
        fis.close();
        fos.close();

    }

    /**
     *  3. 利用通道完成文件的复制(直接缓冲区),该方法优于 2 方法
     */
    @Test
    public void test3(){
        FileChannel inChannel = null;
        FileChannel outChannel = null;
        try {
            inChannel = FileChannel.open(Paths.get("TestChannel.txt"),StandardOpenOption.READ);
            outChannel = FileChannel.open(Paths.get("TestChannel3.txt"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);

            inChannel.transferTo(0, inChannel.size(), outChannel);
            //outChannel.transferFrom(inChannel, 0, inChannel.size());

        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            if( inChannel != null){
                try {
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if( outChannel != null){
                try {
                    outChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }
    }

    /** 
     * 2. 使用内存映射文件操作(直接缓冲区)
     */
    @Test
    public void test2(){
        FileChannel inChannel = null;
        FileChannel outChannel = null;

        //内存映射文件
        MappedByteBuffer inMappedBuf = null;
        MappedByteBuffer outMappedBuf = null;
        try {
            inChannel = FileChannel.open(Paths.get("TestChannel.txt"),StandardOpenOption.READ);
            outChannel = FileChannel.open(Paths.get("TestChannel4.txt"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);

            inMappedBuf = inChannel.map(MapMode.READ_ONLY,0,inChannel.size());
            outMappedBuf = outChannel.map(MapMode.READ_WRITE,0,inChannel.size());

            byte[] buf = new byte[inMappedBuf.limit()];
            inMappedBuf.get(buf);
            outMappedBuf.put(buf);

        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            if( inChannel != null){
                try {
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if( outChannel != null){
                try {
                    outChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }
    }

    /**
     * 1. 利用通道完成文件的复制(非直接缓冲区)
     */
    @Test
    public void test1(){
        FileInputStream fis = null;
        FileOutputStream fos = null;
        FileChannel inChannel = null;
        FileChannel outChannel = null;
        try {
            // 1. 创建文件流
            fis = new FileInputStream("TestChannel.txt");
            fos = new FileOutputStream("TestChannel2.txt");

            // 2. 通过文件流得到通道
            inChannel = fis.getChannel();
            outChannel = fos.getChannel();

            // 3. 创建缓冲区并开辟空间
            ByteBuffer buf = ByteBuffer.allocate(1024);
                // 3.1 通过inChannel通道读到 buf 缓冲区
            while(inChannel.read(buf) != -1){
                // 3.1 缓冲区切换到读取模式
                // 从缓冲区中读取内容写到outChannel中
                buf.flip();
                // 3.2 将缓冲区的内容写入到通道中
                outChannel.write(buf);
                // 3.3 缓冲区清零(position恢复到0,limit和capacity恢复到1024)
                buf.clear();
            }
            fis.close();
            fos.close();
            inChannel.close();
            outChannel.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            if(fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fos != null){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(inChannel != null){
                try {
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(outChannel != null){
                try {
                    outChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

Select选择器和网络通信

使用TCP/IP协议进行测试阻塞模式和非阻塞模式

测试阻塞式和非阻塞式的网络传输

一,使用NIO完成网络通信的三个核心

1. 通道(Channel):负责链接

 java.nio.channels.Channel 接口:
      |--SelectableChannel 
          |--ServerSocketChannel
          |--DatagramChannel

          |--Pipe.SinkChannel
          |--Pipe.SourceChannel

2. 缓冲区(Buffer):负责数据的存取

3. 选择器(Selector)

是SelectableChannel的多路复用器,用来监控SelectableChannel的IO状况

阻塞模式

    /* 阻塞模式 */

    //客户端
    @Test
    public void client() throws IOException{
        // 1. 创建SocketChannel
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8898));

        FileChannel fileChannel = FileChannel.open(Paths.get("1.jpg"),StandardOpenOption.READ);

        // 2. 创建缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        // 3. 发送数据
        while(fileChannel.read(buffer) != -1){
            buffer.flip();
            sChannel.write(buffer);
            buffer.clear();
        }
        // 表示发送结束
        sChannel.shutdownOutput();

        // 4. 接受服务端发送来的反馈信息
        int len = 0;
        while( (len = sChannel.read(buffer)) != -1){
            System.out.println(new String(buffer.array(),0,len));
        }

        //解决线程阻塞
        sChannel.shutdownInput();

        fileChannel.close();
        sChannel.close();
    }

    //服务端
    @Test
    public void server() throws IOException{
        // 1. 创建ServerSocket 套接字,并绑定端口
        ServerSocketChannel ssChannel = ServerSocketChannel.open();

        FileChannel fileChannel = FileChannel.open(Paths.get("2.jpg"),StandardOpenOption.WRITE,StandardOpenOption.CREATE);

        // 2. 绑定端口
        ssChannel.bind(new InetSocketAddress(8898));

        // 3. 创建缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        // 4. 接收数据
        SocketChannel sChannel = ssChannel.accept();
        while(sChannel.read(buffer) != -1){
            buffer.flip(); 
            fileChannel.write(buffer);
            buffer.clear();
        }

        //解决线程阻塞
        sChannel.shutdownInput();

        // 5. 给客户端反馈数据
        buffer.put("服务端已经收到消息!".getBytes());
        buffer.flip();
        sChannel.write(buffer);
        buffer.clear();
        sChannel.shutdownOutput();

        fileChannel.close();
        sChannel.close();
        ssChannel.close();
    }

非阻塞模式

    /* 使用 Selector 选择器的 非阻塞模式 */
    @Test
    public void noBlockClient() throws IOException{
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8898));

        //2. 切换非阻塞模式
        sChannel.configureBlocking(false);

        ByteBuffer buffer  = ByteBuffer.allocate(1024);

        Scanner scanner = new Scanner(System.in);

         /*  如果在 server 端读取 buffer 的时候是 (len=sChannel.read(buffer)) > 0 
          *  一般情况表示,当读取到文件流的末尾的时候就会返回 -1 
          *  但是此处是循环读取用户输入的字符串,并不像文件一样有末尾,所以服务端接受的时候
          *  只能他判断是否把字符串读取完了,应该是 判断是否 >0 
          *  */
        while(scanner.hasNext()){
            String str = scanner.next();
            buffer.put((new Date().toString()+"\n"+str).getBytes());
            buffer.flip();
            sChannel.write(buffer);
            buffer.clear();
        }

        /*  如果在 server 端读取 buffer 的时候是 (len=sChannel.read(buffer)) !=-1 
         *  表示,当读取到文件流的末尾的时候就会返回 -1 
         *  */
//        String str = scanner.next();
//        buffer.put(str.getBytes());
//        buffer.flip();
//        sChannel.write(buffer);
//        buffer.clear();

        scanner.close();
        sChannel.close();
    }

    @Test
    public void noBlockServer() throws IOException{
        // 1. 获取通道
        ServerSocketChannel ssChannel = ServerSocketChannel.open();

        // 2. 配置非阻塞模式
        ssChannel.configureBlocking(false);

        // 3. 绑定端口
        ssChannel.bind(new InetSocketAddress(8898));

        // 4. 获取选择器
        Selector selector = Selector.open();

        // 5. 在选择器上注册该通道,并且指定"监听接收事件"
        ssChannel.register(selector, SelectionKey.OP_ACCEPT);

        // 6. 轮询式的获取选择器上已经“准备就绪”的事件
        while(selector.select() > 0){

            // 7. 获取当前选择器中所有注册的“选择键(已经准备就绪的监听事件)”
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

            while(iterator.hasNext()){

                // 8. 获得准备就绪事件
                SelectionKey sk = iterator.next();

                // 9.判断具体是什么事件准备就绪

                if(sk.isAcceptable()){

                    // 10. 若“接受就绪”,获取当前客户端链接
                    SocketChannel sChannel = ssChannel.accept();

                    // 11. 切换到非阻塞模式
                    sChannel.configureBlocking(false);

                    // 12. 在选择器上注册 OP_READ 读事件监听器
                    sChannel.register(selector, SelectionKey.OP_READ);

                    //sChannel.close();
                }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();
                    }
                    //sChannel.close();
                }
                // 15. 取消选择键 SelectionKey
                iterator.remove();
            }
        }
        ssChannel.close();
    }

UDP传输非阻塞

    /* 测试UDP协议的网络传输
     * UDP 协议没有三次握手
     * 所以不用配置 监听SelectionKey.OP_ACCEPT
     * 直接配置  监听SelectionKey.OP_READ
     * 
     * */
    @Test
    public void tstUdpClient() throws IOException{
        DatagramChannel dChannel = DatagramChannel.open();

        dChannel.configureBlocking(false);

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        Scanner sc = new Scanner(System.in);

        while(sc.hasNext()){
            String str = sc.next();
            buffer.put((new Date().toString()+"\n"+str).getBytes());
            buffer.flip();
            dChannel.send(buffer, new InetSocketAddress("127.0.0.1",8898));
            buffer.clear();
        }

        sc.close();
        dChannel.close();
    }

    @Test
    public void tstUdpServer() throws Exception{
        DatagramChannel dChannel = DatagramChannel.open();

        dChannel.bind(new InetSocketAddress(8898));

        dChannel.configureBlocking(false);

        Selector selector = Selector.open();

        dChannel.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 buf = ByteBuffer.allocate(1024);

                    dChannel.receive(buf);

                    buf.flip();
                    System.out.println(new String(buf.array(),0,buf.limit()));
                    buf.clear();
                }
                //如果读取完毕,取消所有的选择
                iterator.remove();
            }
        }
        dChannel.close();
    }

管道通信


    /* 线程之间管使用道通信
     * 
     * 线程之间通信的时候
     * 2 步骤放在一个线程中
     * 3 步骤放在另外一个线程中
     * 
     *  */
    @Test
    public void testPiper() throws Exception{
        // 1. 获取通道
        Pipe pipe = Pipe.open();

        // 2. 将缓冲区中的数据写入到管道
        ByteBuffer buf = ByteBuffer.allocate(1024);
        buf.put("通过单向管道发送数据".getBytes());

        // 2.1 管道的写通道
        Pipe.SinkChannel sinkChannel = pipe.sink();

        buf.flip();
        sinkChannel.write(buf);
        buf.clear();

        // 3. 读取缓冲区的数据
        // 3.1 管道的读通道
        Pipe.SourceChannel sourceChannel = pipe.source();

        int len = sourceChannel.read(buf);
        buf.flip();
        System.out.println(new String(buf.array(),0,len));
        buf.clear();

        sourceChannel.close();
        sinkChannel.close();


    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值