主要参考:NIO学习文档这个大神写的非常好,通俗易懂!!!!!!!!
注意,“旧”的I/O包已经使用NIO重新实现过,即使我们不显式的使用NIO编程,也能从中受益。
- 面向流
Java IO是面向流的,这意味着你一次从一个流中读取一个或多个字节。如何处理读取的字节由你决定。它们不会被缓存到任何地方。此外,不能 *** 作在数据流中来回移动。如果需要在从流读取的数据中来回移动,则需要先将其缓存到缓冲区中。 - 面向缓冲区
Java NIO的面向缓冲区的方法略有不同。数据被读入一个缓冲区,然后从这个缓冲区中进行处理。您可以根据 *** 作在缓冲区中来回移动。这在处理过程中提供了更多的灵活性。
- Java IO是阻塞的,一次连接对应一个线程,当进行流的读写时,该线程会一直阻塞至读写完毕
- Java NIO是非阻塞的,当线程从通道中读取数据时,若此时没有可用数据,此时线程不会被阻塞,可以被其他资源继续使用,直到有可用数据可被读取。非阻塞写也是如此。
使用单线程控制选择器管理多个通道。
二、NIO三个主要概念
通道通道与流的对比
- 通道可以读也可以写,流一般来说是单向的(只能读或者写,所以之前我们用流进行IO *** 作的时候需要分别创建一个输入流和一个输出流)。
- 通道可以异步读写。
- 通道总是基于缓冲区Buffer来读写。
通道的实现
- FileChannel: 用于文件的数据读写
- DatagramChannel: 用于UDP的数据读写
- SocketChannel: 用于TCP的数据读写,一般是客户端实现
- ServerSocketChannel: 允许我们监听TCP链接请求,每个请求会创建会一个SocketChannel,一般是服务器实现
Java NIO:从Channels读数据到Buffers,从Buffers写数据到Channels
- 容量
- 位置
- 限制
Selector是一个组件,它可以检查一个或多个Java NIO 通道 实例,并确定哪些通道准备好进行读取或写入等 *** 作
使用选择器可以让一个线程处理多个通道,减少线程切换的资源消耗和线程的管理
Java NIO:一个线程使用一个选择器来处理 3 个通道的
非阻塞服务器需要定时检查是否传入数据,以及检查数据的完整性。服务器可能需要多次检查,直到收到一条或多条完整消息,所以是一个定时的查询。
同样,非阻塞服务器需要定时检查是否有数据要写入。如果是,服务器需要检查写入数据的完整性,避免数据被部分写入。
所以服务器的channel需要定时执行的三个检查器是:
- 读管道,新连接数据的传入;
- 检查数据完整性;
- 写管道,检查它是否可以将任何传出的消息写到任何打开的连接上。
需求:上传文件到抖音或者快手等发布渠道,当文件过大,上传时可能出现连接超时等网络问题,渠道官方一般推荐使用分片上传机制,降低大文件上传的失败频率。使用IO的RandomAccessFile完成需求。类似于一次NIO文件 *** 作,针对缓存区做 *** 作,http请求类似于一个文件上传通道。
RandomAccessFile支持对随机访问文件的读写。同时,RandomAccessFile支持“随机访问”的方式。局限的是它只能读写文件。
RandomAccessFile的一个重要使用场景就是网络请求中的多线程下载及断点续传。当分片上传中的某一片上传失败时,可以根据分片号和文件位移量的位置得到需要重新 *** 作的文件片段,进行重新上传,我使用的是@Retryable注解使用在分片上传的方法上。
@Retryable(maxAttempts = 5, backoff = @Backoff(value = 1000, multiplier = 1.5))
四种访问模式:
- “r”
只读 - “rw”
可读可写。如果文件不存在,则会尝试创建它。 - “rws”
可读可写,与“rw”一样,还要求每次对文件内容或元数据的更新都同步写入底层存储设备。 - “rwd”
可读可写,与“rw”一样,还要求每次对文件内容的更新都同步写入底层存储设备。
代码示例:
// 只读模式 RandomAccessFile raf = new RandomAccessFile(file, "r"); try { long length = file.length(); //分片号 从1开始 int startOffset = 1; int byteCount; // 缓存区 取文件长度和每片大小中小的值作为缓存区的容量 byte[] buff = new byte[(int) Math.min(SIZE, length)]; boolean finish = false; while (!finish) { // 指针位置 从 0 开始,每次位移量 = 1 * 每片大小 raf.seek((startOffset - 1) * SIZE); // 每片的字节数 byteCount = raf.read(buff); // 当本次上传的字节数未到达需要分片的大小 | 指针已经到末尾 证明是最后一片 if (byteCount < SIZE || (startOffset) * SIZE == length) { byte[] realChunkData = new byte[byteCount]; System.arraycopy(buff, 0, realChunkData, 0, byteCount); buff = realChunkData; finish = true; } //分片上传 nio:通过Channel管道运输着存储数据的Buffer缓冲区的来实现数据的处理 this.uploadVideoShard(startOffset, buff); // 修改指针位置 未结束每次 + 1 startOffset = finish ? startOffset : startOffset + 1; //分片上传完成 开始文件分片合片 if (finish) { JSONObject result = restTemplate.postForObject(completeUrl, HttpEntity.EMPTY, JSONObject.class); } } } finally { if (file.exists()) { file.delete(); } // 关闭文件 raf.close(); }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)