• NIO基础篇(一)


     1.NIO与传统IO的比较

      Java的NIO(New IO)是不同于旧IO的,旧的IO是基于字节流和字符流的,是阻塞的IO。NIO是基于通道(Channel)和缓冲区(Buffer)的,是非阻塞的IO。

      使用旧IO每次读取一行数据流的流程图如下,Thread必须等待,等待readline读到一行的数据并返回。

      

       使用NIO,可以周期判断Buffer中是否有数据,没有数据时还可以去做其它的事情。基于缓存(Buffer)在一定程度上减少了读写速度不一致所带来的等待。

        

      NIO可以使用单线程管理多个通道(网络连接或文件),但付出的代价是解析数据可能会比从一个阻塞流中读取数据更复杂。如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,例如聊天服务器,实现NIO的服务器可能是一个优势。

       

      如果有少量的连接使用非常高的带宽,一次发送大量的数据,旧IO模型实现可能非常契合。

      

      Java中几种IO的比较:  

      BIO:传统的IO,是同步阻塞IO

      NIO:同步非阻塞IO,轮询缓冲区的状态。

      AIO:  jdk1.7开始支持,异步非阻塞IO

     2.Channel、Buffer和Selector

      NIO的核心组成部分:Channel、Buffer和Selector。

      选择器(Selector)用于监听多通道中的事件。

     

      Channel的主要实现: 

    • FileChannel  : 从文件中读写数据
    • DatagramChannel  :  基于UDP读写网络中的数据
    • SocketChannel : 基于TCP读写网络中的数据
    • ServerSocketChannel  : 监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel

      Buffer的主要实现: 

    • ByteBuffer 
    • CharBuffer 
    • DoubleBuffer 
    • FloatBuffer 
    • IntBuffer 
    • LongBuffer 
    • ShortBuffer

      Buffer中还有一个特殊的类型:MappedByteBuffer,用于实现内存映射。

      

      向Selector注册Channel,然后调用它的select()方法,这个方法会一直阻塞到通道中事件的发生,如新建连接、数据接收等。

     3.Channel示例

      我们将从文件中读取内容并打印,将使用Channel和Buffer。

      文件中的内容如下:

    if you try,you may have a chance!
    if you try,you may have a chance2!
    
    朋友!
    朋友!
    朋友!
    朋友!
    朋友!
    朋友!
    朋友!

      读写的例子程序如下,其中还考虑了中文和读到半个中文字符的情况,只适用于文件编码格式为GBK的情形,这时一个汉字占2个字节。编码为UTF-8时,占用3个字节,需要重新考虑:

    package nio;
    
    import java.io.RandomAccessFile;
    import java.nio.ByteBuffer;
    import java.nio.channels.FileChannel;
    
    public class nio {
    
        public static void main(String[] args) throws Exception {
            nio n = new nio();
            n.readDataToBuffer();
        }
    
        private void readDataToBuffer() throws Exception {
            RandomAccessFile aFile = new RandomAccessFile("test.txt", "rw");
            FileChannel inChannel = aFile.getChannel();
    
            ByteBuffer buf = ByteBuffer.allocate(48);
            
            byte [] bytes = new byte [48];
            int index = 0;
            buf.put("just for test ".getBytes(),3,6);  //直接往buffer中放数据
            int bytesRead = inChannel.read(buf);   //数据通过chanel读到buffer中
            int isChineseCharacterIndex = 0;
            while (bytesRead != -1) { //缓冲区数据大小,-1表示没有数据            
                buf.flip();    //从写模式切换到读模式
                
                while (buf.hasRemaining()) {                
                    bytes[index] = buf.get();
                    if(bytes[index]<0){ //判断在最后读到半个汉字的情况,汉字占2个byte,这2个byte都是小于0的
                        isChineseCharacterIndex ++;
                    }
                    index++;
                }
                if(isChineseCharacterIndex%2 !=0){     //读到了半个汉字的情况,这半个汉字不显示,并将这半个汉字重新加到buffer中
                    System.out.println("Read " + (index-1));
                    System.out.println(new String(bytes,0,index-1));  
                    buf.position(index-1);
                    buf.compact();
                }else{
                    System.out.println("Read " + index);
                    System.out.println(new String(bytes,0,index));
                    buf.clear();
                }
                bytesRead = inChannel.read(buf);
                index = 0;
                isChineseCharacterIndex = 0;
                //bytes = new byte [48];      read new data will override  the old one        
    } inChannel.position(inChannel.size());
    //定位到文件末尾 //inChannel.write(ByteBuffer.wrap(" 朋友!".getBytes())); inChannel.close(); aFile.close(); } }

      输出结果为:

    Read 48
    t for if you try,you may have a chance!
    if you 
    Read 47
    try,you may have a chance2!
    
    朋友!
    朋友!
    朋
    Read 31!
    朋友!
    朋友!
    朋友!
    朋友!

      分析一下上面的代码, ByteBuffer.allocate(48)定义了一个大小为48B的缓冲区,inChannel.read(buf)是通过管道向缓冲区中写入数据,bytesRead != -1是指通过管道向缓冲区中写入的数据流不为空,下面的代码是循环的从缓冲区中读出所写入的数据。

      buf.flip() 是反转Buffer的读写状态,从之前的写状态变为读状态。

      Channel从文件中读数据到Buffer中时,我们也可以直接向Buffer中写入数据。flip切换成写状态时,写入到Channel中的数据也将写入到文件中。

      还需要注意的是:buf.position(index-1); buf.compact();作用是只保留最后一位字符(也就是半个中文字符)并移至Buffer顶端后清空Buffer。

      读完缓冲区中的数据后调用clear方法将清空缓冲区的数据。

     4.Buffer示例

      理解Buffer,首先需要理解position、limit和capacity在读模式和写模式中的不同作用。

      

      position在读模式表示当前写入数据的位置,从0开始,写模式下,position从0增加到capacity-1。读模式下position表示可以读取到的数据的位置,position将一直等于0。

      limit在写模式下等于capacity,表示最多可以写入多少数据。limit在读模式下,表示Buffer中一共有多少个数据。

      capacity在读模式和写模式下的作用相同,都表示最多可以写入多少数据。

      理解position、limit和capacity在读模式和写模式中的不同作用后,还需要了解Buffer中一些常用方法的使用,如下。

      clear(),只是把指针移到位置0,并没有真正清空数据。
      flip(), limit = position,指针指向0,常在读模式和写模式切换时使用。
      rewind(),指针指向0。
      compact(),压缩数据。比如当前capacity是6,当前指针指向2(即0,1的数据已经写出了,没用了),那么compact方法将把2,3,4,5的数据挪到0,1,2,3的位置。
      clear()和rewind()都是为新读入数据做好准备,区别在于clear会将capacity重置,而rewind不会重置capacity。

      关于clear、flip、rewind、compact和clear方法的使用,可以参考下面的例子:

    package nio;
    
    import java.io.RandomAccessFile;
    import java.nio.ByteBuffer;
    import java.nio.channels.FileChannel;
    
    public class nio2 {
        public static void main(String[] args) throws Exception {
            nio2 n = new nio2();
            n.readDataToBuffer();
        }
    
        private void readDataToBuffer() throws Exception {
            RandomAccessFile aFile = new RandomAccessFile("test.txt", "rw");
            FileChannel inChannel = aFile.getChannel();
    
            //一般在把数据写入Buffer前调用clear,一般在从Buffer读出数据前调用flip,一般在把数据重写入Buffer前调用rewind
            //Buffer and it's subclass is not thread safety
            ByteBuffer buf = ByteBuffer.allocate(48);
            
            byte [] chars = new byte [48];
            int index = 0;
            int bytesRead = inChannel.read(buf); 
            System.out.println("============test rewind and clear method begin===============");
            if (bytesRead != -1) { 
                System.out.println("Read " + bytesRead);
                buf.flip(); //change read mode to write mode 
                
                while (buf.hasRemaining()) {                
                    chars[index] = buf.get();
                    index++;
                }
                System.out.println(new String(chars,0,index));  //中文的情况            
            }
            buf.rewind(); //rewind method just reset the pointer position,not clear buffer,and we can get the data again
            index = 0;        
            System.out.println("Read " + bytesRead);
            //buf.flip();   //change write mode to read mode,and when write mode change to read mode,buffer will clear
            //System.out.println("position: " + buf.position()+ ",capacity: " + buf.capacity() + ",limit: " + buf.limit());
            
            //buf.clear();    //clear method just reset the pointer position,not clear buffer,and we can get the data again
            while (buf.hasRemaining()) {                
                chars[index] = buf.get();
                index++;
            }
            System.out.println(new String(chars));
            System.out.println("============test rewind and clear method end===============");
            
            //test compact method,
            System.out.println("============test compact method begin===============");
            buf.position(35);  //assume has read 35 byte,set the pointer to 35
            buf.compact();  //will not clear the buffer,but copy date 35-47 to 0-12,and the pointer to 13
            System.out.println("position: " + buf.position()+ ",capacity: " + buf.capacity() + ",limit: " + buf.limit());
            index = 0;
            for(int i=0;i<13;i++){
                chars[index] = buf.get(i);
                index++;
            }
            
            System.out.println("data before the pointer: " + new String(chars,0,index));
            index = 0;
            while (buf.hasRemaining()) {                
                chars[index] = buf.get();
                index++;
            }
            System.out.println("data after the pointer: " + new String(chars,0,index));
            System.out.println("============test compact method end===============");
            
            //test mark and reset method
            System.out.println("============test mark and reset method begin===============");
            index = 0;
            buf.rewind();
            while (buf.hasRemaining()) {                
                chars[index] = buf.get();
                index++;
                if(index == 13){
                    buf.mark();
                }
            }        
            buf.reset();
            
            System.out.println("position after reset: " + buf.position());
            index = 0;        
            while (buf.hasRemaining()) {                
                chars[index] = buf.get();
                index++;
            }
            System.out.println("data after reset: " + new String(chars,0,index));
            System.out.println("============test mark and reset method end===============");        
            
            inChannel.close();
            aFile.close();
        }
    }

      输出结果为:

            ============test rewind and clear method begin===============
        Read 48
        if you try,you may have a chance!
        if you try,yo
        Read 48
        if you try,you may have a chance!
        if you try,yo
        ============test rewind and clear method end===============
        ============test compact method begin===============
        position: 13,capacity: 48,limit: 48
        data before the pointer: if you try,yo
        data after the pointer: u may have a chance!
        if you try,yo
        ============test compact method end===============
        ============test mark and reset method begin===============
        position after reset: 13
        data after reset: u may have a chance!
        if you try,yo
        ============test mark and reset method end===============

      写数据到Buffer有两种方式:

        从Channel写到Buffer。       int bytesRead = inChannel.read(buf);

        通过Buffer的put()方法写到Buffer里。     buf.put(127);

      从Buffer中读取数据有两种方式:

        从Buffer读取数据到Channel。        int bytesWritten = inChannel.write(buf);

        使用get()方法从Buffer中读取数据。         byte aByte = buf.get();

      还可以直接将一个Channel中的数据直接传输到另一个Channel中,可以见下面的例子: 

    package nio;
    
    import java.io.RandomAccessFile;
    import java.nio.channels.FileChannel;
    
    public class nio3 {
    
        public static void main(String[] args) throws Exception{
            RandomAccessFile fromFile = new RandomAccessFile("test.txt", "rw");
            FileChannel      fromChannel = fromFile.getChannel();
    
            RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
            FileChannel      toChannel = toFile.getChannel();
    
            long position = 0;
            long count = fromChannel.size();
    
            toChannel.transferFrom(fromChannel,position, count);
            //fromChannel.transferTo(position, count, toChannel);
    
            toChannel.close(); //channel关闭时会将数据写入
            fromFile.close();
            toFile.close();
        }
    
    }

      运行代码后,将把test.txt中的内容拷贝到toFile.txt。

      

  • 相关阅读:
    广度遍历有向图
    坚持的力量 第二十一篇
    坚持的力量 第二十二篇
    搜索引擎首页
    安装ubuntu
    最小生成树之Kruskal算法
    最小生成树之PRIM算法
    文件同步软件
    [恢]hdu 2151
    [恢]hdu 1396
  • 原文地址:https://www.cnblogs.com/lnlvinso/p/4541709.html
Copyright © 2020-2023  润新知