1、NIO详解之标准NIO

前言

  NIO即新的输入输出,这个库是在JDK1.4中才引入的。它在标准java代码中提供了高速的面向块的IO操作。NIO即New IO,NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多。
  

  NIO和IO最大的区别是数据打包和传输方式。IO是以流的方式处理数据,而NIO是以块的方式处理数据。
  
  在Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO,

本篇主要介绍 标准NIO

Buffer

  Buffer是一个对象,它包含一些要写入或读出的数据。在NIO中,数据是放入buffer对象的,而在IO中,数据是直接写入或者读到Stream对象的。应用程序不能直接对 Channel 进行读写操作,而必须通过 Buffer 来进行, 即 Channel 是通过 Buffer 来读写数据的。

  当向 Buffer 写入数据时,Buffer 会记录下写了多少数据。一旦要读取数据,需要通过 flip()方法将 Buffer 从写模式切换到读模式。在读模式下,可以读取之前写入到 Buffer 的所有数据。
  一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用clear()compact() 方法。clear()方法会清空整个缓冲区。compact() 方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。
  

Channel

  Channel是一个对象,可以通过它读取和写入数据。可以把它看做IO中的流。但是它和流相比还有一些不同:
  1、Channel是双向的,既可以读又可以写,而流是单向的
  2、 Channel可以进行异步的读写
  3、 对Channel的读写必须通过buffer对象
  
  正如上面提到的,所有数据都通过Buffer对象处理,所以,您永远不会将字节直接写入到Channel中,相反,您是将数据写入到Buffer中;

  同样,您也不会从Channel中读取字节,而是将数据从Channel读入Buffer,再从Buffer获取这个字节。

  在Java NIO中Channel主要有如下几种类型:

FileChannel:从文件读取数据的
DatagramChannel:读写UDP网络协议数据
ServerSocketChannel:可以监听TCP连接
SocketChannel:读写TCP网络协议数据

1、读取数据

原理 :在NIO系统中,任何时候执行一个读操作,不是直接从Channel中读取数据,因为所有的数据都必须用Buffer来封装,所以您应该是从Channel读取数据到Buffer。

  1、    从FileInputStream获取Channel
  2、    创建Buffer
  3、    从Channel读取数据到Buffer

第一步:获取通道

FileInputStream fin = new FileInputStream( "readandshow.txt" );
FileChannel fc = fin.getChannel(); 

第二步:创建缓冲区

ByteBuffer buffer = ByteBuffer.allocate( 1024 );

第三步:将数据从通道读到缓冲区

fc.read( buffer );
public class ReadTest {

    static public void main( String args[] ) throws Exception {
        FileInputStream fin = new FileInputStream( "/Users/healerjean/Desktop/test.txt" );

        // 获取通道
        FileChannel fc = fin.getChannel();

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

        // 读取数据到缓冲区
        fc.read(buffer);
        buffer.flip(); //用来提供后面写入
        while (buffer.remaining()>0) {
            byte b = buffer.get();
            System.out.print(((char)b)); //读取中文会乱码哦
        }
        fin.close();
    }
}

2、写入数据到文件

类似于从文件读数据, 
第一步:获取一个通道

FileOutputStream fout = new FileOutputStream( "writesomebytes.txt" );
FileChannel fc = fout.getChannel();

第二步:创建缓冲区,将数据放入缓冲区

ByteBuffer buffer = ByteBuffer.allocate( 1024 );

for (int i=0; i<message.length; ++i) {
 buffer.put( message[i] );
}
buffer.flip(); //切换buffe到读模式,这样才能被channel读取并且写入到文件中

第三步:把缓冲区数据写入通道中

fc.write( buffer );
  public class WriteTest {


    static public void main( String args[] ) throws Exception {
        String messageString = "HealerJean";
        byte message[] = messageString.getBytes("UTF-8");
        FileOutputStream fout = new FileOutputStream( "/Users/healerjean/Desktop/test.txt" );
        FileChannel fc = fout.getChannel();
        ByteBuffer buffer = ByteBuffer.allocate( 1024 );

        for (int i=0; i<message.length; ++i) {
            buffer.put( message[i] );
        }
        buffer.flip();
        fc.write( buffer );
        fout.close();
    }
}

3、读写结合

**
 * 用java NIO api拷贝文件
 * @param src
 * @param dst
 * @throws IOException
 */
public static void copyFileUseNIO(String src,String dst) throws IOException{
    //声明源文件和目标文件
            FileInputStream fi=new FileInputStream(new File(src));
            FileOutputStream fo=new FileOutputStream(new File(dst));
            //获得传输通道channel
            FileChannel inChannel=fi.getChannel();
            FileChannel outChannel=fo.getChannel();
            //获得容器buffer
            ByteBuffer buffer=ByteBuffer.allocate(1024);
            while(true){
                //判断是否读完文件
                int eof =inChannel.read(buffer);
                if(eof==-1){
                    break;  
                }
                //重设一下buffer的position=0,limit=position
                buffer.flip();
                //开始写
                outChannel.write(buffer);
                //写完要重置buffer,重设position=0,limit=capacity
                buffer.clear();
            }
            inChannel.close();
            outChannel.close();
            fi.close();
            fo.close();
}     

4、解释一下奇怪的方法

4.1、fcin.read( buffer ); 检查状态

  当没有更多的数据时,拷贝就算完成,此时 read() 方法会返回 -1 ,我们可以根据这个方法判断是否读完。

int r= fcin.read( buffer );
if (r==-1) {
     break;
     }

4.2、buffer.flip()、clear(); 切换到读取模式

控制buffer状态的三个变量
  position:跟踪已经写了多少数据或读了多少数据,它指向的是下一个字节来自哪个位置

  limit:代表还有多少数据可以取出或还有多少空间可以写入,它的值小于等于capacity。

  capacity:代表缓冲区的最大容量,一般新建一个缓冲区的时候,limit的值和capacity的值默认是相等的。

  
flip、clear这两个方法便是用来设置这些值的。

1、flip() 源码

public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
 }

  写入数据之前我们调用了buffer.flip();方法,这个方法把当前的指针位置position设置成了limit,再将当前指针position指向数据的最开始端,

  我们现在可以将数据从缓冲区写入通道了。 position 被设置为 0,这意味着我们得到的下一个字节是第一个字节。 limit 已被设置为原来的 position,这意味着它包括以前读到的所有字节,并且一个字节也不多。 这样就是度模式

WX20180328-104825@2x

2、clear

 public final Buffer clear() {
    position = 0;
    limit = capacity;
    mark = -1;
    return this;
}

  写入数据之后也就是读数据之前,我们调用了 buffer.clear();方法,这个方法重设缓冲区以便接收更多的字节。下图显示了在调用 clear() 后缓冲区的状态。

WX20180328-105123@2x

5、具体分析读写过程

1、初始化缓冲区状态

WX20180328-154946@2x

2、写入缓存区数据

WX20180328-154955@2x

3、缓冲区变成可读取模式

WX20180328-155011@2x

4、全部读完

WX20180328-155018@2x

5、读完清空缓冲区

WX20180328-155025@2x





如果满意,请打赏博主任意金额,感兴趣的请下方留言吧。可与博主自由讨论哦

支付包 微信 微信公众号
支付宝 微信 微信公众号
posted @ 2018-04-02 18:09  HealerJean  阅读(221)  评论(0编辑  收藏  举报