java nio

一、什么是java nio

  java nio 是java new i/o的简称,也有叫java non-blocking i/o,在jdk1.4中引入。它是一种同步非阻塞的io模型,也是io多路复用的基础。

二、nio技术组成

  java NIO主要由三部分组成①channels、②selecters、③Buffers 

  1、Channel(通道)类似于bio中的stream,数据可以从channel读取到buffer中,也可以从buffer写入channel中。channel和stream的不同点是stream是单向的,二channel是双向的。下面是一些常见的通道,

    FileChannel  从文件中读写数据,不能设置为非阻塞模式,也就不能与selector在一起使用

    DatagramChannel 通过UDP读写网络中的数据

    SocketChannel  通过TCP读写网络中的数据

    ServerSocketChannel  可以监听新进来的TCP链接,对每一个新进来的TCP链接都会创建一个SocketChannel

  2.Buffers 缓存,用来缓存从channel中读取的数据,也可以将数据从缓存写入channel中。

    ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer  从命名可知分别对应了几种基本数据类型。

    MappedByteBuffer 是ByteBuffer的子类,用于表示内存映射文件缓存,可用于复制大型文件。

  Buffer的属性和一些常用的方法:

    属性:mark <= position <= limit <= capacity

      capacity :缓存区的总容量

      position :缓冲区中,下一次发生读取或写入操作的地方,每次读写操作后,都会向后推进。

      limit:在读模式下,limit和positon一相同;在写模式下,limit设置为上次读取时position的地方

      mark:标记值,当调用了mark()方法后,mark就是当前的position位置;调用reset()之后,会将postion置为mark的位置。

      

    方法:

         allocate(n)   初始化缓冲区的大小,此时position=limit=0 ,capacity  = n, mark= -1

      put()  向缓冲区写数据

      get() 向缓冲区读数据  

      filp() 将缓冲区从写模式切换到读模式   

      clear()  从读模式切换到写模式,不会清空数据,但后续写数据会覆盖原来的数据,即使有部分数据没有读,也会被遗忘;将position和limit都置为0,将mark置为-1

       compact()  从读数据切换到写模式,数据不会被清空,会将所有未读的数据copy到缓冲区头部,后续写数据不会覆盖,而是在这些数据之后写数据

      mark() 对position做出标记,配合reset使用

      reset()  将position置为标记值

      rewind() 将position置为0,mark置为-1

 

  3.selector 选择器,用于监听在selector上注册了的channel的事件,只能用于非阻塞channel。通过调用它的select()或其重载方法,这个方法会一直阻塞,直到注册的通道中有事件就绪。

    在selector上注册channel时需要指定监听的事件,总共有如下四种事件可供选择:

      SelectionKey.OP_CONNECT   如果某个channel成功连接到另一个服务器称为“连接就绪”

      SelectionKey.OP_ACCEPT   如果一个ServerSocketChannel准备好接收新进入的连接称为“接收就绪”

      SelectionKey.OP_READ      如果一个有数据可读的通道可以说是“读就绪”

      SelectionKey.OP_WRITE 等待写数据的通道可以说是“写就绪”

  下面是一些nio方法操作文件的例子:

  

//NIO读写文件
public class Test3 {

    public static void main(String[] args) {
        String path = "E:/a.txt";
        readFile(path);
        String path2 = "E:/b.txt";
        writeFile(path2);
    }
    
    /**
     * 
    * @Title: readFile
    * @Description:使用FileChannel读取文本文件
    * @param @param path
    * @return void
    * @throws 
     */
    public static void readFile(String path){
        FileInputStream fis = null;
        FileChannel fc = null;
        try {
            //Java.nio.charset.Charset处理了字符转换问题。它通过构造CharsetEncoder和CharsetDecoder将字符序列转换成字节和逆转换。
            Charset charset = Charset.forName("utf-8");  
            CharsetDecoder decoder = charset.newDecoder();
            
            fis = new FileInputStream(path);
            fc = fis.getChannel();
            
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024); 
            CharBuffer charBuffer = CharBuffer.allocate(1024);
            while (fc.read(byteBuffer) != -1) { 
                byteBuffer.flip();
                decoder.decode(byteBuffer, charBuffer, false);//如果不够一个字符不会进行解码
                charBuffer.flip();
                while (charBuffer.hasRemaining()) {
                    System.out.print(charBuffer.get());
                } 
                byteBuffer.compact();//将缓冲区设置为读模式,并且上一次没有转换的字节会被保留下来,并放在最前端,不能用clear()代替
                charBuffer.compact();
            }
            fis.close();
            fc.close();
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            try {
                fis.close();
                fc.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
    }
    
    /**
     * 
    * @Title: writeFile
    * @Description:使用FileChannel将文字写入文件中
    * @param @param path
    * @return void
    * @throws 
     */
    public static void writeFile(String path){
        
        FileOutputStream fos = null;
        FileChannel fileChannel = null;
        try {
            String source = "我要写入一个文件中使得发放卡号即可adda";
            fos = new FileOutputStream(path);
            fileChannel = fos.getChannel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            buffer.put(source.getBytes("utf-8"));
            buffer.flip();
            fileChannel.write(buffer);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            try {
                fos.close();
                fileChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
//nio复制文件
public class Test4 {

    public static void main(String[] args) throws IOException {
        String source = "E:/BaiduNetdisk_5.6.3.exe";
        String target = "E:/BaiduNetdisk_5.6.3_copy.exe";
//        bioCopyFile(source,target);
//        nioCopyFile(source,target);
        mappedCopyFile(source,target);
    }
    
    /**
     * 
    * @Title: bioCopyFile
    * @Description: java 标准io流复制文件
    * @param @throws IOException
    * @return void
    * @throws 
     */
    public static void bioCopyFile(String source,String target)throws IOException {
        long startTime = System.currentTimeMillis();
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(source));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(target));
        int len = 0;
        while((len = bis.read()) != -1){
            bos.write(len);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("bio复制文件所用时间"+(endTime-startTime)+"毫秒");
        bis.close();
        bos.close();
    }
    
    /**
     * 
    * @Title: nioCopyFile
    * @Description: java nio复制文件
    * @param @throws IOException
    * @return void
    * @throws 
     */
    public static void nioCopyFile(String source,String target)throws IOException {
        long startTime = System.currentTimeMillis();
        RandomAccessFile raf = new RandomAccessFile(source,"rw");
        RandomAccessFile raf2 = new RandomAccessFile(target,"rw");
        FileChannel inChannel = raf.getChannel();
        FileChannel outChannel = raf2.getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        while(inChannel.read(buffer) != -1){
            buffer.flip();//写模式切换到读模式
            outChannel.write(buffer);
            buffer.clear();//读模式切换到写模式
        }
        raf.close();
        raf2.close();
        inChannel.close();
        outChannel.close();
        long endTime = System.currentTimeMillis();
        System.out.println("nio复制文件所用时间"+(endTime-startTime)+"毫秒");
    }
    
    /**
     * 
    * @Title: mappedCopyFile
    * @Description: 使用MappedByteBuffer来复制文件
    * MappedByteBuffer的确快,但也存在一些问题,主要就是内存占用和文件关闭等不确定问题。
    * 被MappedByteBuffer打开的文件只有在垃圾收集时才会被关闭,而这个点是不确定的
     */
    public static void mappedCopyFile(String source,String target) throws IOException{
        long startTime = System.currentTimeMillis();
        RandomAccessFile raf = new RandomAccessFile(source,"rw");
        RandomAccessFile raf2 = new RandomAccessFile(target,"rw");
        FileChannel inChannel = raf.getChannel();
        FileChannel outChannel = raf2.getChannel();
        MappedByteBuffer mappedByteBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
        outChannel.write(mappedByteBuffer);
        raf.close();
        raf2.close();
        inChannel.close();
        outChannel.close();
        long endTime = System.currentTimeMillis();
        System.out.println("MappedByteBuffer复制文件所用时间"+(endTime-startTime)+"毫秒");
    }

}
//nio服务器
public class Server {
    private Selector selector;//同一个选择器,用来监听是否有链接或者其他操作就绪
    
    
    public static void main(String[] args) {
        Server server = new Server();
        server.startServer();//启动服务器时监听端口是否有新的连接事件
        server.listenChannel();//监听所有的channel有没有数据读取准备就绪
    }
    
    public void startServer(){
        try {
            ServerSocketChannel ssc = ServerSocketChannel.open();
            ssc.configureBlocking(false);
            ssc.socket().bind(new InetSocketAddress(8888));//监听8888端口
            selector = Selector.open();
            ssc.register(selector, SelectionKey.OP_ACCEPT);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public void listenChannel(){
        while(true){
            try {
                selector.select();
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectedKeys.iterator();
                while(iterator.hasNext()){
                    SelectionKey key = iterator.next();
                    handleKey(key);
                    iterator.remove();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    private void handleKey( SelectionKey key){
        try {
            if(key.isAcceptable()){//有新的网络可以链接
                ServerSocketChannel channel = (ServerSocketChannel)key.channel();
                SocketChannel socketChannel = channel.accept();
                socketChannel.configureBlocking(false);
                socketChannel.register(selector, SelectionKey.OP_READ);
            }
            if(key.isReadable()){
                SocketChannel channel = (SocketChannel)key.channel();
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                int read = channel.read(buffer);
                if(read > 0){
                    String receiveText = new String(buffer.array(),0,read);
                    System.out.println("服务器端接受到的数据---"+receiveText);
                    channel.register(selector, SelectionKey.OP_WRITE);
                }
            }
            if(key.isWritable()){
                SocketChannel channel = (SocketChannel)key.channel();
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                buffer.clear();
                buffer.put("服务器响应信息".getBytes());
                buffer.flip();
                channel.write(buffer);
                channel.register(selector, SelectionKey.OP_READ);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

//nio客户端
public class Client {
    
    public static void main(String[] args) {
            clientStart();
    }
    
    public static void clientStart(){
        try {
            Selector selector = Selector.open();
            SocketChannel sc = SocketChannel.open();
            sc.configureBlocking(false);
            sc.connect(new InetSocketAddress("localhost", 8888));
            sc.register(selector, SelectionKey.OP_CONNECT);
            
            
            while(true){
                selector.select();
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectedKeys.iterator();
                while(iterator.hasNext()){
                    SelectionKey key = iterator.next();
                    //iterator.remove();
                    if(key.isConnectable()){
                        System.out.println("client connect");  
                        SocketChannel socketChannel = (SocketChannel) key.channel();
                        // 判断此通道上是否正在进行连接操作。  
                        // 完成套接字通道的连接过程。  
                        ByteBuffer sendBuffer = ByteBuffer.allocate(1024);
                        //完成连接的建立(TCP三次握手)
                        if (socketChannel.isConnectionPending()) {
                            socketChannel.finishConnect(); 
                            System.out.println("完成连接!");  
                            sendBuffer.put("Hello,Server".getBytes());  
                            sendBuffer.flip();  
                            socketChannel.write(sendBuffer);
                        }
                        socketChannel.register(selector, SelectionKey.OP_READ);
                    }
                    
                    if(key.isReadable()){
                        SocketChannel channel = (SocketChannel) key.channel();  
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        int read=channel.read(buffer);  
                        if(read>0){  
                            String receiveText = new String( buffer.array(),0,read);  
                            System.out.println("客户端接受服务器端数据--:"+receiveText);  
                            channel.register(selector, SelectionKey.OP_WRITE);  
                        }  
                    }
                    if(key.isWritable()){
                        SocketChannel socketChannel = (SocketChannel)key.channel();
                        ByteBuffer sendBuffer = ByteBuffer.allocate(1024);
                        sendBuffer.clear();  
                        sendBuffer.put("MSG from client".getBytes());  
                        sendBuffer.flip();  
                        socketChannel.write(sendBuffer);
                        socketChannel.register(selector, SelectionKey.OP_READ); 
                    }
                }
            }
            
        } catch (IOException e) {
            e.printStackTrace();
        }
        
    }
 }

    

  

原文地址:https://www.cnblogs.com/kyleinjava/p/8856180.html