Java NIO(二):标准输入输出NIO

目录

一、流与块的比较

二、NIO核心组件

1、Channel

2、Buffer

3、Selector

三、文件读写的应用

1、从文件中读

2、写入到文件

3、通过NIO复制文件


在Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套是网络编程NIO。

一、流与块的比较

NIO和IO最大的区别是数据打包和传输方式。IO是以的方式处理数据,而NIO是以的方式处理数据。

面向流的IO一次一个字节的处理数据,一个输入流产生一个字节,一个输出流就消费一个字节。为流式数据创建过滤器就变得非常容易,链接几个过滤器,以便对数据进行处理非常方便而简单,但是面向流的IO通常处理的很慢。

面向块的IO系统以块的形式处理数据。每一个操作都在一步中产生或消费一个数据块。按块要比按流快的多,但面向块的IO缺少了面向流IO所具有的简单性。

二、NIO核心组件

  • channel:标准NIO中的核心对象
  • Buffer:标准NIO中的核心对象
  • Selector:网络NIO中中的核心对象

Channel是对原IO中流的模拟,任何来源和目的数据都必须通过一个Channel对象。

一个Buffer实质上是一个容器对象,是对原IO中的缓冲区的模拟,发给Channel的所有对象都必须先放到Buffer中;同样的,从Channel中读取的任何数据都要读到Buffer中。

 

1、Channel

Channel是一个对象,可以通过它读取和写入数据。基本上,所以的IO在NIO中都是从一个Channel开始。Channel有点像流(Stream),数据可以从Channel读取到Buffer,也可以从Buffer写到Channel。所有数据都通过Buffer对象处理!

不过它和流相比还有一些不同:

  1. Channel是双向的,既可以读又可以写,而流是单向的
  2. Channel可以进行异步的读写
  3. 对Channel的读写必须通过buffer对象

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

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

 

2、Buffer

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

Buffer实质上是一个数组,通常是一个字节数据,但也可以是其他类型的数组。但一个缓冲区不仅仅是一个数组,重要的是它提供了对数据的结构化访问,而且还可以跟踪系统的读写进程。

下图描述了从一个客户端向服务端发送数据,然后服务端接收数据的过程:

客户端发送数据时,必须先将数据存入Buffer中,然后将Buffer中的内容写入通道。服务端这边接收数据必须通过Channel将数据读入到Buffer中,然后再从Buffer中取出数据来处理。

使用 Buffer 读写数据一般遵循以下步骤:

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

控制buffer状态的三个变量:

  • position:跟踪已经写了多少数据或读了多少数据,它指向的是下一个字节来自哪个位置
  • limit:代表还有多少数据可以取出或还有多少空间可以写入,它的值小于等于capacity。
  • capacity:代表缓冲区的最大容量,一般新建一个缓冲区的时候,limit的值和capacity的值默认是相等的。flip、clear这两个方法便是用来设置这些值的。

在对Buffer进行读/写的过程中,position会往后移动,而 limit 就是 position 移动的边界。在对Buffer进行写入操作时,limit应当设置为capacity的大小,而对Buffer进行读取操作时,limit应当设置为数据的实际结束位置。(注意:将Buffer数据 写入 通道是Buffer 读取 操作,从通道 读取 数据到Buffer是Buffer 写入 操作)

通过阅读源码就可以知道Buffer对应的几个方法的实现原理:

  • flip(): 设置 limit 为 position 的值,然后 position 置为0。对Buffer进行读取操作前调用。
  • rewind(): 仅仅将 position 置0。一般是在重新读取Buffer数据前调用,比如要读取同一个Buffer的数据写入多个通道时会用到。
  • clear(): 回到初始状态,即 limit 等于 capacity,position 置0。重新对Buffer进行写入操作前调用。
  • compact(): 将未读取完的数据(position 与 limit 之间的数据)移动到缓冲区开头,并将 position 设置为这段数据末尾的下一个位置。其实就等价于重新向缓冲区中写入了这么一段数据。
     

NIO中Buffer的类型:

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

这些Buffer覆盖了你能通过IO发送的数据基本类型。

3、Selector

Selector允许单线程处理多个Channel(一个Channel就是一个连接),如果每个连接的流量都很小,使用Selector就会很方便,如上图所示。而不用每个Channel来新建一个线程处理。

会在之后的文章中谈到。
 

三、文件读写的应用

1、从文件中读

如果从文件读取数据的话,需要如下三步:

1、从FileInputStream获取Channel通道

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

2、创建Buffer缓冲区

ByteBuffer buffer = ByteBuffer.allocate( 1024 );

3、从Channel通道读取数据到Buffer缓冲区

fc.read( buffer );

2、写入到文件

类似于读

1、获取一个Channel通道

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

2、创建Buffer缓冲区,将数据放入缓冲区

ByteBuffer buffer = ByteBuffer.allocate( 1024 );

for (int i=0; i<message.length; ++i) {
     buffer.put( message[i] );
}

buffer.flip();

3、把Buffer缓冲区数据写入Channel通道中

fc.write( buffer );

3、通过NIO复制文件

public static void copyFileWithNIO(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){
        //判断是否读完文件
        if(inChannel.read(buffer)==-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();
} 

参考资料

https://blog.csdn.net/suifeng3051/article/details/48160753

https://www.cnblogs.com/dolphin0520/p/3919162.html

https://blog.csdn.net/geekcome/article/details/23868411

https://blog.csdn.net/javaxuexi123/article/details/81910644

猜你喜欢

转载自blog.csdn.net/qq_39192827/article/details/86561405