一、Java NIO Channel 基本介绍

  1. NIO的通道类似于流,但有些区别如下:
    • 通道可以同时进行读写,而流只能读或者只能写
    • 通道可以实现异步读写数据
    • 通道可以从缓冲读数据,也可以写数据到缓冲:
  2. BIO 中的 Stream 是单向的,例如 FileInputStream 对象只能进行读取数据的操作,而 NIO 中的通道(Channel)是双向的,可以读操作,也可以写操作。
  3. ChannelNIO 中是一个接口 public interface Channel extends Closeable{}

文章开头.jpg

二、Java NIO Channel 主要实现类

**java.nio.channels.Channel** 包下:

  • FileChannel :用于文件的数据读写
  • SocketChannel :SocketChannel类似于Socket
  • ServerSocketChannel :ServerSocketChannel类似于ServerSocket
  • DatagramChannel :用于对UDP数据读写

图示如下:

NIO Channel 介绍(三) - 图2

三、Java NIO 获取通道Channel

  1. Java针对支持通道的类提供了getChannel()方法

本地IO:

  - FileInputStream/FileOutputStream
  - RandomAccessFile

网络IO:

  - Socket
  - ServerSocket
  - DatagramSocket

以上几个类都可以通过调用getChannel()方法获取通道

  1. 在JDK1.7中的NIO.2针对各个通道提供了静态方法open()
  2. 在JDK1.7中的NIO.2的Files工具类的newByteChannel()方法

    四、Java NIO 应用实例

经过我们前后学的NIO 简介 ,NIO Buffer,NIO Channel 写几个案例来熟悉Buffer和Channel

应用实例1-本地文件写数据

实例要求:

  1. 将“你好,世界”写入 hello.txt 文件中
  2. 文件不存在就创建

代码

/**
  *@Description     <h1>把数据写入到文件中</h1>
  *@author          王振宇
  *@create          2021-08-01 15:05
  */
public class NIOFileChannel01  {
    //使用的是FileChannel通道对象 + ByteBuffer字节缓冲区
    public static void main(String[] args)  throws  Exception{
        //1.创建一个字符串
        String str = "你好,世界";
        //2.创建一个输出流
        FileOutputStream fileOutputStream = new FileOutputStream("D:\\object\\IO\\NIO\\src\\main\\resources\\hello.txt");
        //3.根据输出流获取FileChannel通道对象
        //其实 FileChannel真正的为它的实现类FileChannelImpl
        FileChannel channel = fileOutputStream.getChannel();
        //4.创建一个缓冲区 ByteBuffer ,初始容量为1024字节
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        //5.把字符串放在缓冲区中
        byteBuffer.put(str.getBytes());
        //6.对ByteBuffer进行反转 ,因为在put进去的时候position属性已经为不再是0了,需要反转
        byteBuffer.flip();
        //7.把缓冲区的数据写入到通道channel中
        channel.write(byteBuffer);
        //8.关闭资源
        fileOutputStream.close();

    }
}
/**
 * str ====> Buffer ===>[FileOutStream(FileChannel)]===>文件
 */

其中:
    使用了FileChannel通道,ByteBuffer缓存区来完成此项应用实例
    获取通道方法为本文第三块获取方式的第一条,FileOutStream.getChannel()获取
    其中每一步骤的细节,代码注释中都已经解释清楚

应用实例2-本地文件读数据

实例要求:

  1. 将 hello.txt 文件中的内容读取到控制台
  2. 文件假定已经存在

代码:

/**
 * @author 王振宇
 * @Description <h1>文件读到控制台</h1>
 * @create 2021-08-02 1:04
 */
public class NIOFileChannel02 {
    public static void main(String[] args) throws Exception {
        File file = new File("D:\\object\\IO\\NIO\\src\\main\\resources\\hello.txt");
        //1.创建一个文件输入流
        FileInputStream fileInputStream = new FileInputStream(file);
        //2.通过文件输入流获取FileChannel管道===>其实实际为FileChannelImpl
        FileChannel channel = fileInputStream.getChannel();
        //3.创建缓冲区
        ByteBuffer byteBuffer =ByteBuffer.allocate((int) file.length());
        //4.从通道把数据读到缓冲区
        channel.read(byteBuffer);
        //5.把缓冲区的数据数组全部拿出来转换成string,(因为不是一个个读所以不需要管position的当前位置,不需要反转)
        System.out.println(new String(byteBuffer.array()));
        fileInputStream.close();
    }
}
/**
 *  文件===>[FileInputStream(FileChannel)]====>ByteBuffer==>控制台
 */
其中:
    使用了FileChannel通道,ByteBuffer缓存区来完成此项应用实例
    获取通道方法为本文第三块获取方式的第一条,FileInputStream.getChannel()获取
    其中每一步骤的细节,代码注释中都已经解释清楚

应用实例3-完成文件读取、写入

实例要求:

  1. 使用一个Buffer完成文件的读取、写入
  2. 图文说明

NIO Channel 介绍(三) - 图3

代码:

/**
 * @author : [王振宇]
 * @version : [v1.0]
 * @className : NIOFileChannel03
 * @description : [文件拷贝]
 * @createTime : [2021/8/2 10:32]
 * @updateUser : [王振宇]
 * @updateTime : [2021/8/2 10:32]
 * @updateRemark : [初始化代码]
 */
public class NIOFileChannel03 {
    public static void main(String[] args) throws Exception{
        //1.获取输入流,获取通道Channel
        FileInputStream fileInputStream = new FileInputStream("D:\\object\\IO\\NIO\\src\\main\\resources\\1.txt");
        FileChannel inputStreamChannel = fileInputStream.getChannel();
        //2.获取输出流, 获取通道Channel
        FileOutputStream fileOutputStream = new FileOutputStream("D:\\object\\IO\\NIO\\src\\main\\resources\\2.txt");
        FileChannel outputStreamChannel = fileOutputStream.getChannel();
        //3.创建字节缓存区
        ByteBuffer buffer = ByteBuffer.allocate(512);
        //4. 通道到缓存
        while (true){
            //如果文件大,缓存容量小,一次while肯定处理不完,所以每次进来缓存读完以后,清空buffer,也就是标识复位
            buffer.clear();
            int read = inputStreamChannel.read(buffer);
            if (read==-1){
                break;
            }
            //注意写之前先反转buffer
            buffer.flip();
            //5.把缓存到通道(文件)
            outputStreamChannel.write(buffer);
        }
        fileInputStream.close();
        fileOutputStream.close();
    }
}

其中:
    使用了FileChannel通道,ByteBuffer缓存区来完成此项应用实例
    获取通道方法为本文第三块获取方式的第一条,FileInputStream.getChannel()和FileOutputStream.getChannel()获取
    其中每一步骤的细节,代码注释中都已经解释清楚

五、关于 Buffer 和 Channel 的注意事项和细节

  1. **ByteBuffer** 支持类型化的 **put****get****put** 放入的是什么数据类型,**get** 就应该使用相应的数据类型来取出,否则可能有 **BufferUnderflowException** 异常。

    /**
    * @author : [王振宇]
    * @version : [v1.0]
    * @className : NIOBufferGetPut
    * @description : [ByteBuffer 支持类型化的 put 和 get,put 放入的是什么数据类型,get 就应该使用相应的数据类型来取出,否则可能有 BufferUnderflowException 异常。]
    * @createTime : [2021/8/2 11:34]
    * @updateUser : [王振宇]
    * @updateTime : [2021/8/2 11:34]
    * @updateRemark : [描述说明本次修改内容]
    */
    public class NIOBufferGetPut {
     public static void main(String[] args) {
    
         //创建一个 Buffer
         ByteBuffer buffer = ByteBuffer.allocate(64);
    
         //类型化方式放入数据
         buffer.putInt(100);
         buffer.putLong(9);
         buffer.putChar('尚');
         buffer.putShort((short) 4);
    
         //取出
         buffer.flip();
    
         System.out.println(buffer.getInt());
         System.out.println(buffer.getLong());
         System.out.println(buffer.getChar());
         System.out.println(buffer.getShort());
    
     }
    }
    

    image.png
    翻译:
    用于读取int值的相对get方法。在这个缓冲区的当前位置读取接下来的四个字节,根据当前字节顺序将它们组合成一个int值,然后将该位置加4。返回:缓冲区当前位置的int值抛出:BufferUnderfLowException - 如果缓冲区中剩余的字节少于4个

  2. 可以将一个普通 **Buffer** 转成只读 `Buffer``**

`

import java.nio.ByteBuffer;

/**
 * @author : [王振宇]
 * @version : [v1.0]
 * @className : ReadOnlyBuffer
 * @description : [可以将一个普通 Buffer 转成只读 Buffer]
 * @createTime : [2021/8/2 11:36]
 * @updateUser : [王振宇]
 * @updateTime : [2021/8/2 11:36]
 * @updateRemark : [描述说明本次修改内容]
 */
public class ReadOnlyBuffer {
    public static void main(String[] args) {

        //创建一个 buffer
        ByteBuffer buffer = ByteBuffer.allocate(64);

        for (int i = 0; i < 64; i++) {
            buffer.put((byte) i);
        }

        //读取
        buffer.flip();

        //得到一个只读的 Buffer
        ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
        System.out.println(readOnlyBuffer.getClass());

        //读取
        while (readOnlyBuffer.hasRemaining()) {
            System.out.println(readOnlyBuffer.get());
        }
        //如果在一个只读的 Buffer中put就会报异常
        readOnlyBuffer.put((byte) 100); //ReadOnlyBufferException
    }
}

方法:
image.png
如果put异常:
image.png

  1. 前面我们讲的读写操作,都是通过一个 **Buffer** 完成的,**NIO** 还支持通过多个 **Buffer**(即 **Buffer**数组)完成读写操作,即 **Scattering****Gathering** ```java /**
    • @author : [王振宇]
    • @version : [v1.0]
    • @className : ScatteringAndGathering
    • @description : [Buffer的分散和聚集]
    • @createTime : [2021/8/2 12:04]
    • @updateUser : [王振宇]
    • @updateTime : [2021/8/2 12:04]
    • @updateRemark : [描述说明本次修改内容] */

import java.net.InetSocketAddress; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel;

/**

  • Scattering:将数据写入到 buffer 时,可以采用 buffer 数组,依次写入 [分散] ,也就是第一个buffer满了以后进入第二个buffer中
  • Gathering:从 buffer 读取数据时,可以采用 buffer 数组,依次读 */ public class ScatteringAndGathering { public static void main(String[] args) throws Exception {

     //使用serverSocketChannel 和  SocketChannel
     ServerSocketChannel open = ServerSocketChannel.open();
     InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);
     //绑定端口并启动
     open.socket().bind(inetSocketAddress);
     //创建Buffer数组
     ByteBuffer[] buffers = new ByteBuffer[2]; //创建一个长度为2的buffer数组
     buffers[0] = ByteBuffer.allocate(5);  //初始化第一个buffer 容量为5个字节
     buffers[1] = ByteBuffer.allocate(3); //初始化第二个buffer,容量为3个字节
     //等待服务器链接
     SocketChannel socketChannel = open.accept();//当有客户端链接的时候就会返回一个socketChannel对象
     int messageLength = 8; //假定从客户端接收 8 个字节
     while (true){
         int byteRead = 0;
     }
    

    } }

```