java-nio

时光怂恿深爱的人放手 提交于 2020-02-29 11:18:16

javanio可以替换传统的io,对于java 的nio理解,可以联想到io。但是他们也有不一样的地方。

1.传统io和nio区别:

2.Buffer 中的重要概念:

Ø 容量 (capacity) : 表示 Buffer 最大数据容量,缓冲区容量不能为负,并且创建后不能更改。  缓冲区其实就是数据组,因为是数组,所以数据容量一旦固定就不能修改了。

Ø 限制 (limit): 第一个不应该读取或写入的数据的索引,即位于 limit 后的数据不可读写。缓冲区的限制不能为负,并且不能大于其容量。
Ø 位置 (position): 下一个要读取或写入的数据的索引。缓冲区的位置不能为负,并且不能大于其限制
Ø 标记 (mark)与重置 (reset): 标记是一个索引,通过 Buffer 中的 mark() 方法指定 Buffer 中一个特定的 position,之后可以通过调用 reset() 方法恢复到这个 position

标记、 位置、 限制、 容量遵守以下不变式: 0 <= mark <= position <= limit <= capacity

注意:mark初始化为-1,表示没有标记过

3.来段代码理解一下

 

package com.nio.nio;

import java.nio.ByteBuffer;

import org.junit.Test;

/**
 * 一、缓冲区(Buffer):在java NIO中负责数据的存取。缓冲区就是数据。用于存储不同数据类型的数据
 * 
 * 跟局数据类型的不同(boolean除外,其他7种基本类型全有),提供相应类型的缓冲区:
 * ByteBuffer
 * CharBuffer
 * ShortBuffer
 * IntBuffer
 * LongBuffer
 * FloatBuffer
 * DoubleBuffer
 *  
 * 上述缓冲区的管理方式几乎一致,通过allocate()获取缓冲区
 * 最常用的是ByteBuffer,因为无论磁盘还是网络传输的都是Byte。
 * 
 * 有了缓冲区,接下来要往缓冲区存取数据
 * 
 * 二、缓冲区存取数据的两个核心方法:
 * put():存入数据到缓冲区中
 * get():获取缓冲区中的数据
 * 
 * 要想对缓冲区的数据进行正确的存取的话,必须姚先了解几个缓冲区的核心属性
 * 
 * 
 * 三、缓冲区中的四个核心属性:
 * capacity:容量,表示缓冲区中最大存储数据的容量。一旦声明不能改变。(因为缓冲区就是数组,数组大小不可改变)
 * limit:界限,表示缓冲区中可以操作数据的大小。(limit后面的数据不能进行读写)
 * position:位置,表示缓冲区正在操作数据的位置。
 * 
 * position <= limit <= capacity
 * 
 * 
 * mark:标记,表示记录当前position的位置。可以用过reset()恢复到mark的位置。
 * 
 * 0 <= mark <= position <= limit <= capacity
 * 
 * mark初始化为-1,表示没有标记过
 * 
 * 
 * @author HappyBks
 *
 */
public class TestBuffer {

    
    @Test
    public void test1(){
        //1、分配一个是指定大小的缓冲区
        ByteBuffer buf=ByteBuffer.allocate(1024);//指定缓冲区大小capacity为1024
        printBufferStatus(buf, "allocate()");
        
        //2、利用put()方法存入数据到缓冲区中
        final String dataStr="abcde";
        buf.put(dataStr.getBytes());
        printBufferStatus(buf, "put()");
        
        
        //3、切换读取数据模式
        buf.flip();
        printBufferStatus(buf, "flip()");
        
        //4、利用get()读取缓冲区中的数据
        byte[] dst=new byte[buf.limit()];
        buf.get(dst);
        System.out.println(new String(dst, 0 , dst.length));
        printBufferStatus(buf, "get()");
        
        
        //5、调用rewind(),恢复读模式到读之前的初始状态,用于可重复再读一次
        buf.rewind();
        printBufferStatus(buf, "rewind()");
        
        //6、clear()清空缓冲区,回到allocate()申请缓冲区的最初状态。
        //但是请注意,这个恢复到最初状态,指定是buffer的几个核心属性恢复到最初状态,
        //里面的数据依然还在,只不过这些数据处于“被遗忘”状态。
        //所谓被遗忘状态,是指buffer的几个指针都恢复到了初始位置,里面有几个数据,从哪读到哪,已经都不知道了。
        buf.clear();
        printBufferStatus(buf, "clear()");
        //尝试读取被clear()清空之后的缓冲区,数据依然在。(因为clear之后,我们无法之后原来有几个数据,所以读一个)
        System.out.println("clear()之后试着get()一个byte: "+(char)buf.get());
        
    }

    private void printBufferStatus(ByteBuffer buf, String info) {
        System.out.println("---------"+info+"----------");
        System.out.println("position: "+buf.position());
        System.out.println("limit: "+buf.limit());
        System.out.println("capacity: "+buf.capacity());
        System.out.println();
    }
    
    
    @Test
    public void test2(){
        final String dataStr="abcde";
        ByteBuffer buf=ByteBuffer.allocate(1024);
        buf.put(dataStr.getBytes());
        buf.flip();
        byte[] dst=new byte[buf.limit()];
        buf.get(dst, 0, 2);
        System.out.println("get(dst, 0, 2): "+new String(dst, 0, 2));
        System.out.println("get(dst, 0, 2) position: "+buf.position());
        
        //mark():标记
        buf.mark();
        System.out.println("mark() position: "+buf.position());

        
        buf.get(dst, 2, 2);
        System.out.println("get(dst, 2, 2): "+new String(dst, 2, 2));
        System.out.println("get(dst, 2, 2) position: "+buf.position());
        
        //reset():恢复到mark的位置
        buf.reset();
        System.out.println("reset() position: "+buf.position());

        //判断缓冲区中是否还有剩余数据
        if(buf.hasRemaining()){
            //获取缓冲区中农剩余的可以操作的数据量
            System.out.println("buf.remaining(): "+buf.remaining());
        }
        
    }
}

 

第一个例子演示了NIOByte缓冲区(其他数据类型的缓冲区操作方式大同小异)申请、写入、切换读模式、读取、可重复读、清空几个基本操作的使用方法,以及在这每个操作阶段缓冲区几个核心属性capacitylimitposition的变化。

第二个例子演示了读取时,如何标记特定位置、之后重置回到改位置重新读取、以及判断读取是否还有剩余、获知还剩多少未读的方法,以及每个操作阶段缓冲区几个核心属性position的变化。

 

4.直接缓存区和非直接缓存区:

看下一章直接缓冲区和非直接缓冲区的区别

5.通道概念

6.选择器

7.网络nio

 

ackage com.nio.nio;

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;

import org.junit.Test;

/**
 * 
 * 一、使用NIO完成网络通信的三个核心:
 * 1. 通道(Channel):负责连接
 *     java.nio.channels.Channel
 *         |-- SelectableChannel
 *             |-- SocketChannel
 *             |-- ServerSocketChannel
 *             |-- DatagramChannel
 * 
 *             |-- Pipe.SinkChannel
 *             |-- Pipe.SourceChannel
 * (注意:FileChannel不能使用非阻塞模式!!!选择其主要监控网络Channel)
 * 
 * 2. 缓冲区(Buffer):负责数据的存取
 * 3. 选择器(Selector):是SelectableChannel的多路复用器。用于监控SelectableChannel的IO状况
 * 
 *
 */
public class TestBlockingNIO {

    //客户端
    @Test
    public void client() throws IOException{
        //1、获取通道(open这种方法是jdk1.7之后才引入的)
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));
        
        //2、分配指定大小的缓冲区
        ByteBuffer buf=ByteBuffer.allocate(1024);
        
        //3、从本地读取文件,并发送到服务端
        FileChannel inFileChannel=FileChannel.open(Paths.get("D:/Test/NIO/1.jpg"), StandardOpenOption.READ);
        while(inFileChannel.read(buf)!=-1){
            buf.flip();
            socketChannel.write(buf);
            buf.clear();
        }
        
        //4、关闭通道
        inFileChannel.close();
        socketChannel.close();
    }
    
    //服务端
    @Test
    public void server() throws IOException{
        //1、获取端口
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    
        //2、绑定连接
        serverSocketChannel.bind(new InetSocketAddress(8888));
        
        //3、获取客户端连接的通道
        SocketChannel socketChannel = serverSocketChannel.accept();
        
        //4、接收客户端的数据,保存到本地。(提到本地,就要弄个FileChannel)
        FileChannel outFileChannel = FileChannel.open(Paths.get("D:/Test/NIO/2.jpg"), StandardOpenOption.WRITE,StandardOpenOption.CREATE);
        ByteBuffer buf=ByteBuffer.allocate(1024);
        while(socketChannel.read(buf)!=-1){
            buf.flip();
            outFileChannel.write(buf);
            buf.clear();
        }
            
        socketChannel.close();
        outFileChannel.close();
        serverSocketChannel.close();
        
    }
}

 

package com.nio.nio;

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;

import org.junit.Test;

/**
 * 
 * 一、使用NIO完成网络通信的三个核心:
 * 1. 通道(Channel):负责连接
 *     java.nio.channels.Channel
 *         |-- SelectableChannel
 *             |-- SocketChannel
 *             |-- ServerSocketChannel
 *             |-- DatagramChannel
 * 
 *             |-- Pipe.SinkChannel
 *             |-- Pipe.SourceChannel
 * (注意:FileChannel不能使用非阻塞模式!!!选择其主要监控网络Channel)
 * 
 * 2. 缓冲区(Buffer):负责数据的存取
 * 3. 选择器(Selector):是SelectableChannel的多路复用器。用于监控SelectableChannel的IO状况
 * 
 *
 */
public class TestBlockingNIO {

    //客户端
    @Test
    public void client() throws IOException{
        //1、获取通道(open这种方法是jdk1.7之后才引入的)
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));
        
        //2、分配指定大小的缓冲区
        ByteBuffer buf=ByteBuffer.allocate(1024);
        
        //3、从本地读取文件,并发送到服务端
        FileChannel inFileChannel=FileChannel.open(Paths.get("D:/Test/NIO/1.jpg"), StandardOpenOption.READ);
        while(inFileChannel.read(buf)!=-1){
            buf.flip();
            socketChannel.write(buf);
            buf.clear();
        }
/*        
        //在阻塞IO下,如果关闭socketChannel,那么服务端不知道客户端是否已经把所有数据发完,所以会一直阻塞。
        socketChannel.shutdownOutput();
        //另一种方法就是把这个线程切换成非阻塞模式
        */
        
        //接收服务端反馈
        int len = 0;
        while((len = socketChannel.read(buf)) !=-1){
            buf.flip();
            System.out.println(new String(buf.array(),0,len));
        }
        
        //4、关闭通道
        inFileChannel.close();
        socketChannel.close();
    }
    
    //服务端
    @Test
    public void server() throws IOException{
        //1、获取端口
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    
        //2、绑定连接
        serverSocketChannel.bind(new InetSocketAddress(8888));
        
        //3、获取客户端连接的通道
        SocketChannel socketChannel = serverSocketChannel.accept();
        
        //4、接收客户端的数据,保存到本地。(提到本地,就要弄个FileChannel)
        FileChannel outFileChannel = FileChannel.open(Paths.get("D:/Test/NIO/2.jpg"), StandardOpenOption.WRITE,StandardOpenOption.CREATE);
        ByteBuffer buf=ByteBuffer.allocate(1024);
        while(socketChannel.read(buf)!=-1){
            buf.flip();
            outFileChannel.write(buf);
            buf.clear();
        }
        
        //发送反馈给客户端
        buf.put("服务端接收数据成功!".getBytes());
        buf.flip();
        socketChannel.write(buf);
        
        socketChannel.close();
        outFileChannel.close();
        serverSocketChannel.close();
        
    }
}
public class TestBlockingNIO {

    //客户端
    @Test
    public void client() throws IOException{
        //1、获取通道(open这种方法是jdk1.7之后才引入的)
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));
        
        //2、分配指定大小的缓冲区
        ByteBuffer buf=ByteBuffer.allocate(1024);
        
        //3、从本地读取文件,并发送到服务端
        FileChannel inFileChannel=FileChannel.open(Paths.get("D:/Test/NIO/1.jpg"), StandardOpenOption.READ);
        while(inFileChannel.read(buf)!=-1){
            buf.flip();
            socketChannel.write(buf);
            buf.clear();
        }
        
        //在阻塞IO下,如果关闭socketChannel,那么服务端不知道客户端是否已经把所有数据发完,所以会一直阻塞。
        socketChannel.shutdownOutput();
        //另一种方法就是把这个线程切换成非阻塞模式
        
        
        //接收服务端反馈
        int len = 0;
        while((len = socketChannel.read(buf)) !=-1){
            buf.flip();
            System.out.println(new String(buf.array(),0,len));
        }
        
        //4、关闭通道
        inFileChannel.close();
        socketChannel.close();
    }
    
    //服务端
    @Test
    public void server() throws IOException{
        //1、获取端口
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    
        //2、绑定连接
        serverSocketChannel.bind(new InetSocketAddress(8888));
        
        //3、获取客户端连接的通道
        SocketChannel socketChannel = serverSocketChannel.accept();
        
        //4、接收客户端的数据,保存到本地。(提到本地,就要弄个FileChannel)
        FileChannel outFileChannel = FileChannel.open(Paths.get("D:/Test/NIO/2.jpg"), StandardOpenOption.WRITE,StandardOpenOption.CREATE);
        ByteBuffer buf=ByteBuffer.allocate(1024);
        while(socketChannel.read(buf)!=-1){
            buf.flip();
            outFileChannel.write(buf);
            buf.clear();
        }
        
        //发送反馈给客户端
        buf.put("服务端接收数据成功!".getBytes());
        buf.flip();
        socketChannel.write(buf);
        
        socketChannel.close();
        outFileChannel.close();
        serverSocketChannel.close();
        
    }
}
package com.nio.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.Date;
import java.util.Iterator;

import org.junit.Test;

public class TestNonBlockingNIO {

    //客户端
    @Test
    public  void client() throws IOException{
        //1、获取通道
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8888));
        //2、切换非阻塞模式
        socketChannel.configureBlocking(false);
        
        //3、分配指定大小的缓冲区
        ByteBuffer buf=ByteBuffer.allocate(1024);
        
        //4、发送数据服务器
        buf.put(new Date().toString().getBytes());
        buf.flip();
        socketChannel.write(buf);
        buf.clear();
        
        //5、关闭通道
        socketChannel.close();
    }
    
    //服务端
    @Test
    public  void server() throws IOException{
        //1、获取通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //2、切换非阻塞模式
        serverSocketChannel.configureBlocking(false);
        
        //3、绑定连接
        serverSocketChannel.bind(new InetSocketAddress(8888));
        
        //4、获取选择器
        Selector selector = Selector.open();
    
        //5、将通道注册到选择器上(第二个选项参数叫做选择键,用于告诉选择器需要监控这个通道的什么状态或者说什么事件(读、写、连接、接受))
        //选择键是整型值,如果需要监控该通道的多个状态或事件,可以将多个选择键用位运算符“或”“|”来连接
        //这里服务端首先要监听客户端的接受状态
        serverSocketChannel.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 socketChannel = serverSocketChannel.accept();
                    
                    //11、客户端连接socketChannel也需要切换非阻塞模式
                    socketChannel.configureBlocking(false);
                    
                    //12、将该通道注册到选择器上,监控客户端socketChannel的读就绪事件
                    socketChannel.register(selector, SelectionKey.OP_READ);
                }
                else if(sk.isReadable()){
                    //13、获取当前选择器上“读就绪”状态的通道
                    SocketChannel socketChannel = (SocketChannel) sk.channel();
                    
                    //14、读取数据
                    ByteBuffer buf=ByteBuffer.allocate(1024);
                    
                    int len=0;
                    while((len=socketChannel.read(buf))>0){
                        buf.flip();
                        System.out.println(new String(buf.array(),0,len));
                        buf.clear();
                    }
                }
                
                //15、取消选择键SelectionKey
                iterator.remove();
            }
            
        }
        
        
        
        
    }
}

 

 

8.udpnoi

 

9.管道

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!