NIO编程

本文深入探讨了Java NIO与标准IO的区别,包括NIO的非阻塞特性、基于通道和缓冲区的数据处理方式,以及选择器的概念。同时,详细介绍了Buffer的操作,如数据存取、标记与重置,直接缓冲区与非直接缓冲区的区别,以及字符集的编码与解码过程。

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

1、 IO\NIO的区别

IO

  • 阻塞
  • 面向于流传输
  • 单向

NIO

  • 非阻塞
  • 面向缓冲区
  • 管道与缓冲区传输数据双向传输

2、NIO概述

Java NIO提供了与标准IO不同的IO工作方式。

  • Channels and Buffers(通道和缓冲区)
    标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。
  • Non-blocking IO(非阻塞IO)
    Java NIO可以让你非阻塞的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似。
  • Selectors(选择器)
    Java NIO引入了选择器的概念,选择器用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。
    注意:传统IT是单向。 NIO类似
    在这里插入图片描述

3、Buffer的数据存取

Java NIO中的Buffer主要用于与NIO通道进行交互,数据是从通道读入到缓冲区,从缓冲区写入通道中的。
Buffer就像一个数组,可以保存多个相同类型的数据。根据类型不同(boolean除外),有以下Buffer常用子类:

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

1)容量(capacity):表示Buffer最大数据容量,缓冲区容量不能为负,并且建立后不能修改。
2)限制(limit):第一个不应该读取或者写入的数据的索引,即位于limit后的数据不可以读写。缓冲区的限制不能为负,并且不能大于其容量(capacity)。
3)位置(position):下一个要读取或写入的数据的索引。缓冲区的位置不能为负,并且不能大于其限制(limit)。
4)标记(mark)与重置(reset):标记是一个索引,通过Buffer中的mark()方法指定Buffer中一个特定的position,之后可以通过调用reset()方法恢复到这个position。

在这里插入图片描述

public class Test004 {

	public static void main(String[] args) {
		// 1.指定缓冲区大小1024
		ByteBuffer buf = ByteBuffer.allocate(1024);
		System.out.println("--------------------");
		System.out.println(buf.position());
		System.out.println(buf.limit());
		System.out.println(buf.capacity());
		// 2.向缓冲区存放5个数据
		buf.put("link1".getBytes());
		System.out.println("--------------------");
		System.out.println(buf.position());
		System.out.println(buf.limit());
		System.out.println(buf.capacity());
		// 3.开启读模式
		// 	flip()开启读模式;position=0;读取时position增加;读取时limit减少
		buf.flip();
		System.out.println("----------开启读模式...----------");
		System.out.println(buf.position());
		System.out.println(buf.limit());
		System.out.println(buf.capacity());
		byte[] bytes = new byte[buf.limit()];
		buf.get(bytes);
		System.out.println(new String(bytes, 0, bytes.length));
		System.out.println("----------重复读模式...----------");
		// 4.开启重复读模式
		// 	rewind()重复读取;position=0
		buf.rewind();
		System.out.println(buf.position());
		System.out.println(buf.limit());
		System.out.println(buf.capacity());
		byte[] bytes2 = new byte[buf.limit()];
		buf.get(bytes2);
		System.out.println(new String(bytes2, 0, bytes2.length));
		// 5.clean 清空缓冲区  数据依然存在,只不过数据被遗忘
		System.out.println("----------清空缓冲区...----------");
		buf.clear();
		System.out.println(buf.position());
		System.out.println(buf.limit());
		System.out.println(buf.capacity());
		System.out.println((char)buf.get());
	}

}

make与rest用法

标记(mark)与重置(reset):标记是一个索引,通过Buffer中的mark()方法指定Buffer中一个特定的position,之后可以通过调用reset()方法恢复到这个position。

public class Test002 {

	public static void main(String[] args) {
		ByteBuffer buf = ByteBuffer.allocate(1024);
		String str = "abcd1";
		buf.put(str.getBytes());
		// 开启读取模式
		buf.flip();
		byte[] dst = new byte[buf.limit()];
		buf.get(dst, 0, 2);
		buf.mark();
		System.out.println(new String(dst, 0, 2));
		System.out.println(buf.position());
		buf.get(dst, 2, 2);
		System.out.println(new String(dst, 2, 2));
		System.out.println(buf.position());
		buf.reset();
		System.out.println("重置恢复到mark位置..");
		System.out.println(buf.position());
	}
}

4、直接缓冲区与非直接缓冲区别

非直接缓冲区:通过 allocate() 方法分配缓冲区,将缓冲区建立在 JVM 的内存中

  • 需要放到JVM
  • 来回拷贝:从内存拷贝到JVM内存
  • 安全

在这里插入图片描述
直接缓冲区:通过 allocateDirect() 方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率

  • 存放到物理内存
  • 不需要来回拷贝,效率较高
  • 不安全
    在这里插入图片描述

字节缓冲区要么是直接的,要么是非直接的。如果为直接字节缓冲区,则 Java 虚拟机会尽最大努力直接在此缓冲区上执行本机 I/O 操作。也就是说,在每次调用基础操作系统的一个本机 I/O 操作之前(或之后),虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区中(或从中间缓冲区中复制内容)。
直接字节缓冲区可以通过调用此类的 allocateDirect() 工厂方法来创建。此方法返回的缓冲区进行分配和取消分配所需成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此,它们对应用程序的内存需求量造成的影响可能并不明显。所以,建议将直接缓冲区主要分配给那些易受基础系统的本机 I/O 操作影响的大型、持久的缓冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。
直接字节缓冲区还可以通过 FileChannel 的 map() 方法 将文件区域直接映射到内存中来创建。该方法返回MappedByteBuffer 。 Java 平台的实现有助于通过 JNI 从本机代码创建直接字节缓冲区。如果以上这些缓冲区中的某个缓冲区实例指的是不可访问的内存区域,则试图访问该区域不会更改该缓冲区的内容,并且将会在访问期间或稍后的某个时间导致抛出不确定的异常。
字节缓冲区是直接缓冲区还是非直接缓冲区可通过调用其 isDirect() 方法来确定。提供此方法是为了能够在性能关键型代码中执行显式缓冲区管理。

// 使用直接缓冲区完成文件的复制(内存映射文件)
	static public void test2() throws IOException {
		long start = System.currentTimeMillis();
		FileChannel inChannel = FileChannel.open(Paths.get("f://1.mp4"), StandardOpenOption.READ);
		FileChannel outChannel = FileChannel.open(Paths.get("f://2.mp4"), StandardOpenOption.WRITE,
				StandardOpenOption.READ, StandardOpenOption.CREATE);
		// 内存映射文件
		MappedByteBuffer inMappedByteBuf = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
		MappedByteBuffer outMappedByteBuffer = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
		// 直接对缓冲区进行数据的读写操作
		byte[] dsf = new byte[inMappedByteBuf.limit()];
		inMappedByteBuf.get(dsf);
		outMappedByteBuffer.put(dsf);
		inChannel.close();
		outChannel.close();
		long end = System.currentTimeMillis();
		System.out.println(end - start);
	}

	// 1.利用通道完成文件的复制(非直接缓冲区)
	static public void test1() throws IOException { // 4400
		long start = System.currentTimeMillis();
		FileInputStream fis = new FileInputStream("f://1.mp4");
		FileOutputStream fos = new FileOutputStream("f://2.mp4");
		// 获取通道
		FileChannel inChannel = fis.getChannel();
		FileChannel outChannel = fos.getChannel();
		// 分配指定大小的缓冲区
		ByteBuffer buf = ByteBuffer.allocate(1024);
		while (inChannel.read(buf) != -1) {
			buf.flip();// 切换为读取数据
			// 将缓冲区中的数据写入通道中
			outChannel.write(buf);
			buf.clear();
		}
		outChannel.close();
		inChannel.close();
		fos.close();
		fis.close();
		long end = System.currentTimeMillis();
		System.out.println(end - start);
	}

通道(Channel)的原理获取

通道表示打开到 IO 设备(例如:文件、套接字)的连接。若需要使用 NIO 系统,需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。Channel 负责传输, Buffer 负责存储。通道是由 java.nio.channels 包定义的。 Channel 表示 IO 源与目标打开的连接。Channel 类似于传统的“流”。只不过 Channel本身不能直接访问数据, Channel 只能与Buffer 进行交互。

  1. Java 针对支持通道的类提供了 getChannel() 方法
    本地 IO:
    FileInputStream/FileOutputStream
    RandomAccessFile

     网络IO:
     Socket
     ServerSocket
     DatagramSocket
    
// 使用直接缓冲区完成文件的复制(內存映射文件)
	public void test2() throws IOException {
		FileChannel inChannel = FileChannel.open(Paths.get("1.png"), StandardOpenOption.READ);
		FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.READ, StandardOpenOption.WRITE,
				StandardOpenOption.CREATE);
		// 映射文件
		MappedByteBuffer inMapperBuff = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
		MappedByteBuffer outMapperBuff = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
		// 直接对缓冲区进行数据读写操作
		byte[] dst = new byte[inMapperBuff.limit()];
		inMapperBuff.get(dst);
		outMapperBuff.put(dst);
		outChannel.close();
		inChannel.close();
	}

	@Test
	// 1.利用通道完成文件复制(非直接缓冲区)
	public void test1() throws IOException {
		FileInputStream fis = new FileInputStream("1.png");
		FileOutputStream fos = new FileOutputStream("2.png");
		// ①获取到通道
		FileChannel inChannel = fis.getChannel();
		FileChannel outChannel = fos.getChannel();

		// ②分配指定大小的缓冲区
		ByteBuffer buf = ByteBuffer.allocate(1024);
		while (inChannel.read(buf) != -1) {
			buf.flip();// 切换到读取模式
			outChannel.write(buf);
			buf.clear();// 清空缓冲区
		}
		// 关闭连接
		outChannel.close();
		inChannel.close();
		fos.close();
		fis.close();
	}

分散读取与聚集写入

分散读取(scattering Reads):将通道中的数据分散到多个缓冲区中

在这里插入图片描述

聚集写入(gathering Writes):将多个缓冲区的数据聚集到通道中

在这里插入图片描述


RandomAccessFile raf1 = new RandomAccessFile("test.txt", "rw");
		// 1.获取通道
		FileChannel channel = raf1.getChannel();
		// 2.分配指定大小的指定缓冲区
		ByteBuffer buf1 = ByteBuffer.allocate(100);
		ByteBuffer buf2 = ByteBuffer.allocate(1024);
		// 3.分散读取
		ByteBuffer[] bufs = { buf1, buf2 };
		channel.read(bufs);
		for (ByteBuffer byteBuffer : bufs) {
			// 切换为读取模式
			byteBuffer.flip();
		}
		System.out.println(new String(bufs[0].array(), 0, bufs[0].limit()));
		System.out.println("------------------分算读取线分割--------------------");
		System.out.println(new String(bufs[1].array(), 0, bufs[1].limit()));
		// 聚集写入
		RandomAccessFile raf2 = new RandomAccessFile("2.txt", "rw");
		FileChannel channel2 = raf2.getChannel();
		channel2.write(bufs);

字符集 Charset

编码:字符串->字节数组
解码:字节数组 -> 字符串

public class Test005 {

	public static void main(String[] args) throws CharacterCodingException {
		// 获取编码器
		Charset cs1 = Charset.forName("GBK");
		// 获取编码器
		CharsetEncoder ce = cs1.newEncoder();
		// 获取解码器
		CharsetDecoder cd = cs1.newDecoder();
		CharBuffer cBuf = CharBuffer.allocate(1024);
		cBuf.put("蚂蚁课堂牛逼!");
		cBuf.flip();
		// 编码
		ByteBuffer bBuf = ce.encode(cBuf);
		for (int i = 0; i < 12; i++) {
			System.out.println(bBuf.get());
		}
		// 解码
		bBuf.flip();
		CharBuffer cBuf2 = cd.decode(bBuf);
		System.out.println(cBuf2.toString());
		System.out.println("-------------------------------------");
		Charset cs2 = Charset.forName("GBK");
		bBuf.flip();
		CharBuffer cbeef = cs2.decode(bBuf);
		System.out.println(cbeef.toString());
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值