前言

Github:https://github.com/HealerJean

博客:http://blog.healerjean.com

  NIO即新的输入输出,这个库是在JDK1.4中才引入的。它在标准java代码中提供了高速的面向块的IO操作。NIO即New IO,NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多。
  NIO和IO最大的区别是数据打包和传输方式。IO是以流的方式处理数据,而NIO是以块的方式处理数据。
     在Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO(异步Io),

本篇主要介绍 标准NIO__

Channel 是通过 Buffer 来读写数据的

1、Buffer

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

  当向 Buffer 写入数据时,Buffer 会记录下写了多少数据。

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

1.1、控制buffer状态的三个变量

  position:跟踪已经写了多少数据或读了多少数据,它指向的是下一个字节的位置
  limit:代表还有多少数据可以取出或还有多少空间可以写入(其实是一回事),它的值小于等于capacity。
  capacity:代表缓冲区的最大容量,一般新建一个缓冲区的时候,limit的值和capacity的值默认是相等的。

1、图文分析读写过程

1、初始化缓冲区状态

ByteBuffer.allocate(capacity)


// 创建缓冲区,就表示创建了一个容量为1024字节的ByteBuffer对象
 ByteBuffer buffer = ByteBuffer.allocate(1024);
(limit=capacity ,position=0)

WX20180328-154946@2x

2、写入缓存区数据

WX20180328-154955@2x

2.1、从channel中读取数据到缓冲区

将数据从channel读入buffer,返回的是缓冲区里面的字节数,如果里面没有数据,则返回-1,如果数据量小一下子就写进来了

channel.read( buffer );
            

2、2、自定义数据到写入缓冲区

   // 2、创建缓冲区,就表示创建了一个容量为1024字节的ByteBuffer对象
 ByteBuffer buffer = ByteBuffer.allocate(1024);

  // 3、缓冲区中放入数据
  byte[] text = "ABCDEFG".getBytes() ;
  for (int i=0; i<text.length; ++i) {
      buffer.put( text[i] );
  }

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

WX20180328-155011@2x

3.2、buffer.flip() 切换到读取模式

只有切换到读模式才能被其他人使用

  写入数据之前我们调用了buffer.flip();方法,这个方法把当前的指针位置position设置成了limit。再将当前指针position指向数据的最开始端
  position 被设置为 0,这意味着我们得到的下一个字节是第一个字节。
  limit 已被设置为原来的 position,这意味着它包括以前读到的所有字节,并且一个字节也不多。 这样就是读取模式

WX20180328-104825@2x

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

 如果没有,就是从文件最后开始读取的,当然读出来的都是byte=0的字符。(所以我们在其他地方使用的时候,就注意了,也就是说看情况,不是非它不可以读取的,因为还有其他的方法啊,笨笨,比如)

ByteBuffer.array()  返回的 array 长度为 ByteBuffer allocate的长度并不是里面所含的内容的长度

注意后面的trim没有的话就会打印所有buffer缓冲区长度的内容打印出来的结果就是正常数据+类似乱码

  byte[] data = buffer.array();
  String msg = new String(data).trim();
  System.out.println(msg);
            

4、全部读完

buffer.get() 按照缓冲区字节读取

 
  //5、按照缓冲区字节读取
  while (buffer.remaining()>0) {
      byte b = buffer.get();
      System.out.print(((char)b)); //读取中文会乱码哦
  }    


 public final int remaining() {
        return limit - position;
 }
     

此时 position = limit

WX20180328-155018@2x

5、读完清空缓冲区

WX20180328-155025@2x

5.1、buffer.clear() 清除缓冲区

 清空缓冲区内容,相当于重置初始化缓冲区


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

 WX20180328-105123@2x   

3、Channel

Channel是一个对象(通道),可以通过它读取和写入数据。可以把它看做IO中的流。但是它和流相比还有一些不同:
  1、Channel是双向的,既可以读又可以写,而流是单向的
  2、Channel可以进行异步的读写
  3、对Channel的读写必须通过buffer对象

  正如上面提到的,所有数据都通过Buffer对象处理,所以,您永远不会将字节直接写入到Channel中,相反,您是将数据写入到Buffer中;同样,您也不会从Channel中读取字节,而是将数据从Channel读入Buffer,再从Buffer获取这个字节。

3.1、 Channel类型

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


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

4、读取数据


    @Test
    public void readFileByChannelToBuffer(){

        try {
            FileInputStream fileInputStream = new FileInputStream("src/main/java/com/hlj/nio/D01Nio/file.txt") ;
            // 1、获取通道
            FileChannel channel = fileInputStream.getChannel();

            // 2、创建缓冲区,就表示创建了一个容量为1024字节的ByteBuffer对象
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            //3、读取数据到缓冲区
            int n  =  channel.read(buffer); //返回的是缓冲区里面的字节数

            //4、读取模式
            buffer.flip();

            //5、按照缓冲区字节读取
            while (buffer.remaining()>0) {
                byte b = buffer.get();
                System.out.print(((char)b)); //读取中文会乱码哦
            }

            //6、读取完成清空缓冲区
            buffer.clear();

            //7、关闭channel
            channel.close();
            fileInputStream.close();


        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    

2、写入数据


    @Test
    public void writeFileByChannelToBuffer(){
        try {
            FileOutputStream fileOutputStream = new FileOutputStream("src/main/java/com/hlj/nio/D01Nio/D02写入数据/write.txt") ;
            // 1、获取通道
            FileChannel channel = fileOutputStream.getChannel();

            // 2、创建缓冲区,就表示创建了一个容量为1024字节的ByteBuffer对象
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            // 3、缓冲区中放入数据
            byte[] text = "ABCDEFG".getBytes() ;
            for (int i=0; i<text.length; ++i) {
                buffer.put( text[i] );
            }

            // 4、切换buffe到读模式,这样才能被channel读取并且写入到文件中
            buffer.flip() ;

            //5、将缓冲区的写入到管道中去
            channel.write(buffer);

            //6、关闭channel
            channel.close();
            fileOutputStream.close();


        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

3、读写结合


package com.hlj.nio.D01Nio.D03读写结合;

import org.apache.tomcat.jni.Buffer;
import org.junit.Test;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * @Description
 * @Author HealerJean
 * @Date 2018/3/27  下午6:41.
 */
public class ReadAndWriteStudy {

    public static void main(String[] args) {

    }

    @Test
    public void readToWrite(){
        String read = "src/main/java/com/hlj/nio/D01Nio/D03读写结合/read.txt";
        String write = "src/main/java/com/hlj/nio/D01Nio/D03读写结合/write.txt";
        copyFileUseNIO(read,write);
    }

    /**
     * 用java NIO api拷贝文件
     * @param src
     * @param dst
     * @throws IOException
     */
    public  void copyFileUseNIO(String src,String dst){
        try {
            FileInputStream fileInputStream = new FileInputStream(new File(src));
            FileOutputStream fileOutputStream=new FileOutputStream(new File(dst));
            //1、获得传输通道channel
            FileChannel readChannel=fileInputStream.getChannel();
            FileChannel wrieteChannel=fileOutputStream.getChannel();

            //2、获得容器buffer,代表每次可以最多写入1024个字节
            ByteBuffer buffer=ByteBuffer.allocate(1024);

            while (true){
                //3、读取数据到缓冲区,每次最多1024个字节
                int n = readChannel.read(buffer);
                if(n==-1){
                    break;
                }

                //3、切换到读模式,这样才能被被channel读取,写入到文件中去
                buffer.flip() ;
                wrieteChannel.write(buffer);

                //4、清空缓冲区,用于 readChannel.read(buffer) 进行写入
                buffer.clear() ;

            }

            readChannel.close();
            wrieteChannel.close();
            fileOutputStream.close();
            fileInputStream.close();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

ContactAuthor