记录NIO模型的三大核心(笔记)

本文详细介绍了Java NIO中的缓冲区(Buffer)概念、作用、常用子类以及ByteBuffer的创建和属性。涵盖了Channel的概念、NIO与BIO的区别,展示了FileChannel、ByteBuffer的实例操作,包括数据复制、scatter/gather和选择器(Selector)的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

缓冲区(buffer)

  • 定义: 一个用于特定基本数据类型的容器
  • 作用:用于与channel进行数据的交互,数据都是从通道读入缓冲区的,从缓冲区写入通道中去

在这里插入图片描述

子类:

Buffer 就像一个数组,可以保存多个相同类型的数据。根 据数据类型不同 ,有以下 Buffer 常用子类:

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer
    创建方法 : ByteBuffer . allocate(int capacity) 里面可以指定自定义的字段类型
缓冲区的基本属性
  • 容量 (capacity) :作为一个内存块,Buffer具有一定的固定大小,也称为"容量",缓冲区容量不能为负,并且创建后不能更改。
  • 限制 (limit):表示缓冲区中可以操作数据的大小(limit 后数据不能进行读写)。缓冲区的限制不能为负,并且不能大于其容量。 写入模式,限制等于buffer的容量。读取模式下,limit等于写入的数据量
  • 位置 (position):下一个要读取或写入的数据的索引。缓冲区的位置不能为 负,并且不能大于其限制
  • 标记 (mark)与重置 (reset):标记是一个索引,通过 Buffer 中的 mark() 方法 指定 Buffer 中一个特定的 position,之后可以通过调用 reset() 方法恢复到这 个 position.
    标记、位置、限制、容量遵守以下不变式: 0 <= mark <= position <= limit <= capacity

ByteBuffer 结构

  • capacity
  • position
  • limit

初始化状态
在这里插入图片描述
切换写模式。写入四个字节
在这里插入图片描述
调用filp()方法切换可读模式
在这里插入图片描述
全部读取完毕的时候,position发生了变化
在这里插入图片描述
执行clear()操作
在这里插入图片描述
compact 方法,是把未读完的部分向前压缩,然后切换至写模式
在这里插入图片描述

相关操作

  • Buffer clear() 清空缓冲区并返回对缓冲区的引用
  • Buffer flip() 为 将缓冲区的界限设置为当前位置,并将当前位置充值为 0
  • int capacity() 返回 Buffer 的 capacity 大小
  • boolean hasRemaining() 判断缓冲区中是否还有元素
  • int limit() 返回 Buffer 的界限(limit)的位置 Buffer limit(int n) 将设置缓冲区界限为 n, 并返回一个具有新 limit 的缓冲区对象
  • Buffer mark() 对缓冲区设置标记
  • int position() 返回缓冲区的当前位置 position
  • Buffer position(int n) 将设置缓冲区的当前位置为 n , 并返回修改后的 Buffer 对象
  • int remaining() 返回 position 和 limit 之间的元素个数
  • Buffer reset() 将位置 position 转到以前设置的 mark 所在的位置
  • Buffer rewind() 将位置设为为 0, 取消设置的 mark

实例化操作演示

public class BufferTest {

	@Test
	public void test03() {
		// 1、创建一个直接内存的缓冲区
		ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
		System.out.println(buffer.isDirect());
	}

	
	// 遍历输出buffer内容
	@Test
	public void tst() {
		ByteBuffer buffer = ByteBuffer.allocate(10);
		String a = "asd";
		// String 类型转换为 byte
		buffer.put(a.getBytes());
		buffer.flip();
		// System.out.println(buffer.get(a.getBytes(),0,buffer.limit()));
		while (buffer.hasRemaining()) {
			System.out.print((char) buffer.get());
		}
		System.out.println();
		int i = 0;
		while (i < a.length()) {
			System.out.print((char) buffer.get(i));
			i++;
		}
		System.out.println();
		System.out.println("String ");
		System.out.println(new String(a.getBytes(),0,buffer.limit()));
		
		buffer.clear();
		System.out.println(buffer.limit());

	}
}

在这里插入图片描述

	@Test
	public void test02() {
		// 1、分配一个缓冲区,容量设置成10
		ByteBuffer buffer = ByteBuffer.allocate(10);
		System.out.println(buffer.position()); // 0
		System.out.println(buffer.limit()); // 10
		System.out.println(buffer.capacity()); // 10
		System.out.println("----------------------------");
		String name = "itheima";
		buffer.put(name.getBytes());
		System.out.println(buffer.position()); // 7
		System.out.println(buffer.limit()); // 10
		System.out.println(buffer.capacity()); // 10
		System.out.println("--------------------------");
		// 2、clear清除缓冲区中的数据
		buffer.clear();
		System.out.println(buffer.position()); // 0
		System.out.println(buffer.limit()); // 10
		System.out.println(buffer.capacity()); // 10
		System.out.println((char) buffer.get());

		// 3、定义一个缓冲区
		ByteBuffer buf = ByteBuffer.allocate(10);
		String n = "=================";
		buf.put(n.getBytes());

		buf.flip();

		// 读取数据
		byte[] b = new byte[2];
		buf.get(b);
		String rs = new String(b);
		System.out.println(rs);

		System.out.println(buf.position()); // 2
		System.out.println(buf.limit()); // 7
		System.out.println(buf.capacity()); // 10
		System.out.println("---------------------");
		buf.mark(); // 标记此刻这个位置! 2

		byte[] b2 = new byte[3];
		buf.get(b2);
		System.out.println(new String(b2));
		System.out.println(buf.position()); // 5
		System.out.println(buf.limit()); // 7
		System.out.println(buf.capacity()); // 10

		buf.reset(); // 回到标记位置
		if (buf.hasRemaining()) {
			System.out.println(buf.remaining());
		}
	}

在这里插入图片描述

Channel通道

1、 定义:Channel 类似于传统的“流”。只不过 Channel 本身不能直接访问数据,Channel 只能与 Buffer 进行交互。

  • NIO 的通道类似于流,但有些区别如下:
  • 通道可以同时进行读写,而流只能读或者只能写

  • 通道可以实现异步读写数据

  • 通道可以从缓冲读数据,也可以写数据到缓冲:

2、BIO 中的 stream 是单向的,例如 FileInputStream 对象只能进行读取数据的操作,而 NIO 中的通道(Channel)
是双向的,可以读操作,也可以写操作。

3、Channel 在 NIO 中是一个接口

常用的Channel实现类

  • FileChannel:用于读取、写入、映射和操作文件的通道。
  • DatagramChannel:通过 UDP 读写网络中的数据通道。
  • SocketChannel:通过 TCP 读写网络中的数据。
  • ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。 【ServerSocketChanne 类似 ServerSocket , SocketChannel 类似 Socket】

FileChannel 类

  • 获取通道的一种方式是对支持通道的对象调用getChannel() 方法。支持通道的类如下:

      * FileInputStream
      * FileOutputStream
      * RandomAccessFile
      * DatagramSocket
      * Socket
      * ServerSocket
        获取通道的其他方式是使用 Files 类的静态方法 newByteChannel() 获取字节通道。或者通过通道的静态方法 open() 打开并返回指定通道
    
  • 常用方法

  • int read(ByteBuffer dst) 从 从 Channel 到 中读取数据到 ByteBuffer
  • long read(ByteBuffer[] dsts) 将 将 Channel 到 中的数据“分散”到 ByteBuffer[]
  • int write(ByteBuffer src) 将 将 ByteBuffer 到 中的数据写入到 Channel
  • long write(ByteBuffer[] srcs) 将 将 ByteBuffer[] 到 中的数据“聚集”到 Channel
  • long position() 返回此通道的文件位置
  • FileChannel position(long p) 设置此通道的文件位置
  • long size() 返回此通道的文件的当前大小
  • FileChannel truncate(long s) 将此通道的文件截取为给定大小
  • void force(boolean metaData) 强制将所有对此通道的文件更新写入到存储设备中

实例

写入操作

@Test
    public void write(){
        try {
            // 1、字节输出流通向目标文件
            FileOutputStream fos = new FileOutputStream("data01.txt");
            // 2、得到字节输出流对应的通道Channel
            FileChannel channel = fos.getChannel();
            // 3、分配缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            buffer.put("hello,黑马Java程序员!".getBytes());
            // 4、把缓冲区切换成写出模式
            buffer.flip();
            channel.write(buffer);
            channel.close();
            System.out.println("写数据到文件中!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

读取操作

 @Test
    public void read() throws Exception {
        // 1、定义一个文件字节输入流与源文件接通
        FileInputStream is = new FileInputStream("data01.txt");
        // 2、需要得到文件字节输入流的文件通道
        FileChannel channel = is.getChannel();
        // 3、定义一个缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        // 4、读取数据到缓冲区
        channel.read(buffer);
        buffer.flip();
        // 5、读取出缓冲区中的数据并输出即可
        String rs = new String(buffer.array(),0,buffer.remaining());
        System.out.println(rs);

    }

使用Buffer完成文件复制

@Test
    public void copy() throws Exception {
        // 源文件
        File srcFile = new File("C:\\Users\\a.jpg");
        File destFile = new File("C:\\Users\\new.jpg");
        // 得到一个字节字节输入流
        FileInputStream fis = new FileInputStream(srcFile);
        // 得到一个字节输出流
        FileOutputStream fos = new FileOutputStream(destFile);
        // 得到的是文件通道
        FileChannel isChannel = fis.getChannel();
        FileChannel osChannel = fos.getChannel();
        // 分配缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        while(true){
            // 必须先清空缓冲然后再写入数据到缓冲区
            buffer.clear();
            // 开始读取一次数据
            int flag = isChannel.read(buffer);
            if(flag == -1){
                break;
            }
            // 已经读取了数据 ,把缓冲区的模式切换成可读模式
            buffer.flip();
            // 把数据写出到
            osChannel.write(buffer);
        }
        isChannel.close();
        osChannel.close();
        System.out.println("复制完成!");
    }

分散 (Scatter) 和聚集 (Gather)

分散读取(Scatter ):是指把Channel通道的数据读入到多个缓冲区中去

聚集写入(Gathering )是指将多个 Buffer 中的数据“聚集”到 Channel。

 @Test
    public void test() throws Exception {
        // 1、字节输入管道
        FileInputStream is = new FileInputStream("data01.txt");
        FileChannel isChannel = is.getChannel();
        // 2、字节输出流管道
        FileOutputStream fos = new FileOutputStream("data02.txt");
        FileChannel osChannel = fos.getChannel();
        // 3、定义多个缓冲区做数据分散
        ByteBuffer buffer1 = ByteBuffer.allocate(4);
        ByteBuffer buffer2 = ByteBuffer.allocate(1024);
        ByteBuffer[] buffers = {buffer1 , buffer2};
        // 4、从通道中读取数据分散到各个缓冲区
        isChannel.read(buffers);
        // 5、从每个缓冲区中查询是否有数据读取到了
        for(ByteBuffer buffer : buffers){
            buffer.flip();// 切换到读数据模式
            System.out.println(new String(buffer.array() , 0 , buffer.remaining()));
        }
        // 6、聚集写入到通道
        osChannel.write(buffers);
        isChannel.close();
        osChannel.close();
        System.out.println("文件复制~~");
    }

transferFrom()
指定目标文件的复制

  @Test
    public void test02() throws Exception {
        // 1、字节输入管道
        FileInputStream is = new FileInputStream("data01.txt");
        FileChannel isChannel = is.getChannel();
        // 2、字节输出流管道
        FileOutputStream fos = new FileOutputStream("data04.txt");
        FileChannel osChannel = fos.getChannel();
        // 3、复制数据
        // osChannel.transferFrom(isChannel , isChannel.position() , isChannel.size());
        isChannel.transferTo(isChannel.position() , isChannel.size() , osChannel);
        isChannel.close();
        osChannel.close();
        System.out.println("完成复制!");
    }

transferTo()

@Test
public void test02() throws Exception {
    // 1、字节输入管道
    FileInputStream is = new FileInputStream("data01.txt");
    FileChannel isChannel = is.getChannel();
    // 2、字节输出流管道
    FileOutputStream fos = new FileOutputStream("data04.txt");
    FileChannel osChannel = fos.getChannel();
    // 3、复制
    isChannel.transferTo(isChannel.position() , isChannel.size() , osChannel);
    isChannel.close();
    osChannel.close();
}

selector选择器

  • 概述:选择器(Selector) 是 SelectableChannle 对象的多路复用器,Selector 可以同时监控多个 SelectableChannel 的 IO 状况,也就是说,利用 Selector可使一个单独的线程管理多个 Channel。Selector 是非阻塞 IO 的核心
  • Java 的 NIO,用非阻塞的 IO 方式。可以用一个线程,处理多个的客户端连接,就会使用到 Selector(选择器)
  • Selector 能够检测多个注册的通道上是否有事件发生(注意:多个 Channel 以事件的方式可以注册到同一个
    Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管
    理多个通道,也就是管理多个连接和请求。
  • 只有在 连接/通道 真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都
    创建一个线程,不用去维护多个线程
  • 避免了多线程之间的上下文切换导致的开销
//1. 获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
//2. 切换非阻塞模式
ssChannel.configureBlocking(false);
//3. 绑定连接
ssChannel.bind(new InetSocketAddress(9898));
//4. 获取选择器
Selector selector = Selector.open();
//5. 将通道注册到选择器上, 并且指定“监听接收事件”
ssChannel.register(selector, SelectionKey.OP_ACCEPT);

当调用 register(Selector sel, int ops) 将通道注册选择器时,选择器对通道的监听事件,需要通过第二个参数
ops 指定。可以监听的事件类型(用 可使用 SelectionKey 的四个常量 表示):

  • 读 : SelectionKey.OP_READ (1)
  • 写 : SelectionKey.OP_WRITE (4)
  • 连接 : SelectionKey.OP_CONNECT (8)
  • 接收 : SelectionKey.OP_ACCEPT (16)
  • 若注册时不止监听一个事件,则可以使用“位或”操作符连接。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

唐 昊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值