NIO复习(2):channel
上篇学习了NIO的buffer,继续来学习channel,类图如下(注:为了不让图看起来太复杂,隐藏了一些中间的接口)

Channel派生了很多子接口,其中最常用的有FileChannel(用于文件操作)以及SocketChannel、ServerSocketChannel(用于网络通讯),下面用几段示例代码学习其基本用法:
一、文件写入
1.1 入门示例
public static void fileWriteReadSimpleDemo() throws IOException {
String filePath = "/tmp/yjmyzz.txt";
//文件写入
String fileContent = "菩提树下的杨过";
FileOutputStream outputStream = new FileOutputStream(filePath);
FileChannel writeChannel = outputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put(fileContent.getBytes());
byteBuffer.flip();//别忘记了,反转position,否则此时position已经移到最后1个有效字符处,下一行将读不到数据
//缓冲区的数据,通过channel写入文件
writeChannel.write(byteBuffer);
writeChannel.close();
//文件读取
File file = new File(filePath);
FileInputStream inputStream = new FileInputStream(file);
FileChannel readChannel = inputStream.getChannel();
//注:这里要重新指定实际大小,否则byteBuffer前面初始化成1024长度,文件内容不足1024字节,
// 后面的空余部分全是默认0填充,最终转换成字符串时,填充的0,也会转换成不可见字符输出
byteBuffer = ByteBuffer.allocate((int) file.length());
readChannel.read(byteBuffer);
System.out.println(new String(byteBuffer.array()));
readChannel.close();
}
FileOutputStream类中内嵌了一个FileChannel的实例,通过getChannel()方法可以获取引用。写文件缓冲区初始化时,如何设置正确的大小,这个不太好掌握,设置太大浪费内存,设置太小又装不下,正确姿势可参考下面的示例2
1.2 缓冲区不够大时循环写入
public static void writeFileDemo() throws IOException {
String fileContent = "菩提树下的杨过(http://yjmyzz.cnblogs.com/)\n\n" +
"送柴侍御\n" +
"【作者】王昌龄 【朝代】唐\n" +
"沅水通波接武冈,送君不觉有离伤。\n" +
"青山一道同云雨,明月何曾是两乡。\n";
//故意设置一个很小的缓冲区,演示缓冲区不够大的情况
ByteBuffer byteBuffer = ByteBuffer.allocate(5);
String filePath = "/tmp/yjmyzz.txt";
FileOutputStream outputStream = new FileOutputStream(filePath);
FileChannel writeChannel = outputStream.getChannel();
//将文件内容,按缓冲区大小拆分成一段段写入
byte[] src = fileContent.getBytes();
int pages = (src.length % byteBuffer.capacity() == 0) ? (src.length / byteBuffer.capacity())
: (src.length / byteBuffer.capacity() + 1);
for (int i = 0; i < pages; i++) {
int start = i * byteBuffer.capacity();
int end = Math.min(start + byteBuffer.capacity() - 1, src.length - 1);
for (int j = start; j <= end; j++) {
byteBuffer.put(src[j]);
}
byteBuffer.flip();
writeChannel.write(byteBuffer);
//记得清空
byteBuffer.clear();
}
writeChannel.close();
}
注意:文件读取时,直接通过File对象的length可以提前知道缓冲的大小,能精确指定Buffer大小,不需要类似这么复杂的循环处理。
二、文件复制
public static void copyFileDemo() throws IOException {
String srcFilePath = "/tmp/yjmyzz.txt";
File srcFile = new File(srcFilePath);
String targetFilePath = "/tmp/yjmyzz.txt.bak";
FileInputStream inputStream = new FileInputStream(srcFile);
FileOutputStream outputStream = new FileOutputStream(targetFilePath);
FileChannel inputChannel = inputStream.getChannel();
FileChannel outputChannel = outputStream.getChannel();
//文件复制
ByteBuffer buffer = ByteBuffer.allocate((int) srcFile.length());
inputChannel.read(buffer);
buffer.flip();
outputChannel.write(buffer);
//也可以用这一行,搞定文件复制(推荐使用)
// outputChannel.transferFrom(inputChannel, 0, srcFile.length());
inputChannel.close();
outputChannel.close();
}

三、文件修改
场景:某个文件需要把最后1个汉字,修改成其它字。先写一段代码,生成测试用的文件
public static void writeLargeFile() throws IOException {
String content = "12345678-abcdefg-菩提树下的杨过\n";
String filePath = "/tmp/yjmyzz.txt";
FileOutputStream outputStream = new FileOutputStream(filePath);
FileChannel writeChannel = outputStream.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(128);
buffer.put(content.getBytes());
for (int i = 0; i < 10; i++) {
buffer.flip();
writeChannel.write(buffer);
}
writeChannel.close();
}
运行完后,测试文件中的内容如下:
12345678-abcdefg-菩提树下的杨过
12345678-abcdefg-菩提树下的杨过
12345678-abcdefg-菩提树下的杨过
12345678-abcdefg-菩提树下的杨过
12345678-abcdefg-菩提树下的杨过
12345678-abcdefg-菩提树下的杨过
12345678-abcdefg-菩提树下的杨过
12345678-abcdefg-菩提树下的杨过
12345678-abcdefg-菩提树下的杨过
12345678-abcdefg-菩提树下的杨过
3.1 常规方法示例
public static void modify1() throws IOException {
String filePath = "/tmp/yjmyzz.txt";
File file = new File(filePath);
FileInputStream inputStream = new FileInputStream(file);
FileChannel inputChannel = inputStream.getChannel();
ByteBuffer buffer = ByteBuffer.allocate((int) file.length());
byte[] tempBytes = "佛".getBytes();
inputChannel.read(buffer);
buffer.flip();
//修改最后1个汉字
for (int i = 0; i < tempBytes.length; i++) {
//最后有一个回车符,然后汉字utf-8占3个字节,所以这里要减4,才是最后1个汉字
int j = buffer.limit() - 4 + i;
buffer.put(j, tempBytes[i]);
}
FileOutputStream outputStream = new FileOutputStream(filePath);
FileChannel outputChannel = outputStream.getChannel();
outputChannel.write(buffer);
inputChannel.close();
outputChannel.close();
}
运行完后,从下面的截图可以看到,测试最后1个字,从“过”变成了“佛”:

这个方法,对于小文件而言没什么问题,但如果文件是一个几G的巨无霸,会遇到2个问题:

首先是allocate方法,只接受int型参数,对于几个G的大文件,File.length很有可能超过int范围,无法分配足够大的缓冲。其次,就算放得下,几个G的内容全放到内存中,也很可能造成OOM,所以需要其它办法。
3.2 利用RandomAccessFile及Channel.map修改文件
public static void modify2() throws IOException {
String filePath = "/tmp/yjmyzz.txt";
RandomAccessFile file = new RandomAccessFile(filePath, "rw");
FileChannel channel = file.getChannel();
//将最后一个汉字映射到内存中
MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, file.length() - 4, 3);
byte[] lastWordBytes = "新".getBytes();
//这样就直接在内存中修改了文件,不再需要调用channel.write
mappedByteBuffer.put(lastWordBytes);
channel.close();
}
这个方法相对就高级多了,RandomAccessFile类是File类的加强版,允许以游标的方式,直接读取文件的某一部分,另外Channel.map方法,可以直接将文件中的某一部分映射到内存,在内存中直接修MappedByteBuffer后,文件内容就相应的修改了。

值得一提的是,从上面调试的截图来看,FileChannel.map方法返回的MappedByteBuffer,真实类型是它下面派生的子类DirectByteBuffer,这是“堆外”内存,不在JVM 自动垃圾回收的管辖范围。
参考文章:
https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/nio/channels/Channel.html
NIO复习(2):channel的更多相关文章
- NIO之通道(Channel)的原理与获取以及数据传输与内存映射文件
通道(Channel) 由java.nio.channels包定义的,Channel表示IO源与目标打开的连接,Channel类似于传统的“流”,只不过Channel本身不能直接访问数据,Channe ...
- 5. 彤哥说netty系列之Java NIO核心组件之Channel
你好,我是彤哥,本篇是netty系列的第五篇. 简介 上一章我们一起学习了如何使用Java原生NIO实现群聊系统,这章我们一起来看看Java NIO的核心组件之一--Channel. 思维转变 首先, ...
- JAVA NIO复习笔记
1. JAVA NIO是什么? 从JDK1.4开始,java提供了一系列改进的输入/输出处理的新功能,这些功能被统称为新IO(New IO,简称NIO),新增了许多用于处理输入/输出的类,这些类都被放 ...
- Java NIO 之 Socket Channel
在Java NIO中用Channel来对程序与进行I/O操作主体的连接关系进行抽象,这些IO主体包括如文件.Socket或其他设备.简而言之,指代了一种与IO操作对象间的连接关系. 按照Channel ...
- NIO相关概念之Channel
通道(Channel)是java.nio的第二个主要创新.它们既不是一个扩展也不是一项增强,而是全新.极好的Java I/O示例,提供与I/O服务的直接连接.Channel用于在字节缓冲区和位于通道另 ...
- Java NIO学习笔记---Channel
Java NIO 的核心组成部分: 1.Channels 2.Buffers 3.Selectors 我们首先来学习Channels(java.nio.channels): 通道 1)通道基础 通道( ...
- NIO的Buffer&Channel&Selector
java的NIO和AIO Buffer position.limit.capacity 初始化 Buffer 填充 Buffer 提取 Buffer 中的值 mark() & reset() ...
- NIO复习03
SocketChannel: 1. Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道.可以通过以下2种方式创建SocketChannel: 打开一个SocketChan ...
- NIO复习02
Selector 1. Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件.这样,一个单独的线程可以管理多个channel,从而管 ...
- NIO复习01
NIO 概述: 1. Java NIO 由以下几个核心部分组成:Channels Buffers Selectors 2. 主要Channel的实现:FileChann ...
随机推荐
- C#多线程编程精要:从用户线程到线程池的效能进化论
1. 引言 在多线程编程中,线程是实现并发执行的核心.C#作为一种功能强大的现代编程语言,提供了丰富的线程管理机制,以支持开发者应对各种并发场景.不同的线程类型在功能.生命周期和适用场景上各有侧重.理 ...
- 【记录】Python|Python3程序测试速度的整个流程、方法对比和选取方式
参考:Python3.7中time模块的time().perf_counter()和process_time()的区别 其他的博客太!长!了!我实在看不下去了,每次都不记得什么场景用什么函数. 让我来 ...
- 106套Axure RP大数据可视化大屏模板及通用组件库
106套Axure RP大数据可视化大屏模板包括了多种实用美观的可视化组件库及行业模板库,行业模板涵盖:金融.教育.医疗.政府.交通.制造等多个行业提供设计参考. 随着大数据的发展,可视化大屏在各行各 ...
- 如何用JavaScript纯前端来实现下载脚本
1.javascript脚本 function downloadFile(data, fileName, type="text/plain") { // 创建不可见的元素 cons ...
- 鸿蒙NEXT(三):从TypeScript到ArkTS的适配详解
@charset "UTF-8"; .markdown-body { line-height: 1.75; font-weight: 400; font-size: 15px; o ...
- footer固定在页面底部的几种方法(转载)
几种非常不错的方法,收藏学习:原文见https://blog.csdn.net/m0_37070714/article/details/77587753 方法一:footer高度固定+绝对定位 < ...
- 玩转C++11多线程:让你的程序飞起来的std::thread终极指南
大家好,我是小康. 你还在为 C++ 多线程编程发愁吗?别担心,今天咱们就用大白话彻底搞定std::thread!看完这篇,保证你对C++11多线程的理解从"一脸懵逼"变成&quo ...
- Web前端入门第 59 问:JavaScript 条件语句中善用 return 让代码更清晰
条件语句 JS 的条件语句不太多,就 if 和 switch 两个,不过他们的使用方式也可以算是眼花缭乱了. if 语句 if 字面意思:如果 xxx.程序中的用法也是这样,如果条件为真,则执行执行代 ...
- 博创Luby使用指南
Luby使用指南 1.开机 通电,当显示在boot界面的时候,长按正方形(深灰色)那个键,即可进入选择程序界面,此时再按一次正方形那个键,即可进入USB连接模式,此时用线将Luby和电脑连接起来. 当 ...
- Springboot笔记<6>Rest的使用和请求参数注解@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody
Rest的使用和原理 Rest风格支持(使用HTTP请求方式动词来表示对资源的操作) • 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveU ...