Java NIO之FileChannel 详解
关键点说明
-
文件打开选项:
-
StandardOpenOption.CREATE
- 文件不存在时创建 -
StandardOpenOption.READ
/WRITE
- 读写权限 -
StandardOpenOption.APPEND
- 追加模式 -
StandardOpenOption.TRUNCATE_EXISTING
- 清空已存在文件
-
-
缓冲区操作:
-
ByteBuffer.wrap()
包装现有字节数组 -
buffer.flip()
切换读写模式 -
直接操作缓冲区提高性能
-
-
高效文件复制:
-
transferTo()
/transferFrom()
方法比传统流复制更高效
-
-
资源管理:
-
使用try-with-resources确保通道自动关闭
-
文件锁需要显式释放
-
这些示例展示了FileChannel的基本读写操作、文件锁的使用以及内存映射文件的高效操作,可以根据实际需求进行调整和扩展。
1. 基本文件读写示例
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class FileChannelDemo {
public static void main(String[] args) {
String filePath = "test.txt";
// 写入文件
writeToFile(filePath, "Hello, FileChannel!");
// 读取文件
String content = readFromFile(filePath);
System.out.println("文件内容: " + content);
// 追加内容
appendToFile(filePath, "\n这是追加的内容");
// 再次读取
System.out.println("追加后的内容: " + readFromFile(filePath));
// 文件复制
copyFile(filePath, "test_copy.txt");
System.out.println("复制文件内容: " + readFromFile("test_copy.txt"));
}
// 写入文件(覆盖)
public static void writeToFile(String filePath, String content) {
try (FileChannel channel = FileChannel.open(
Paths.get(filePath),
StandardOpenOption.CREATE,
StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING)) {
ByteBuffer buffer = ByteBuffer.wrap(content.getBytes());
channel.write(buffer);
System.out.println("写入文件成功");
} catch (IOException e) {
e.printStackTrace();
}
}
// 读取文件
public static String readFromFile(String filePath) {
try (FileChannel channel = FileChannel.open(
Paths.get(filePath),
StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocate((int) channel.size());
channel.read(buffer);
buffer.flip();
return new String(buffer.array(), 0, buffer.limit());
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
// 追加内容到文件
public static void appendToFile(String filePath, String content) {
try (FileChannel channel = FileChannel.open(
Paths.get(filePath),
StandardOpenOption.WRITE,
StandardOpenOption.APPEND)) {
ByteBuffer buffer = ByteBuffer.wrap(content.getBytes());
channel.write(buffer);
System.out.println("追加内容成功");
} catch (IOException e) {
e.printStackTrace();
}
}
// 文件复制
public static void copyFile(String sourcePath, String targetPath) {
try (FileChannel source = FileChannel.open(
Paths.get(sourcePath),
StandardOpenOption.READ);
FileChannel target = FileChannel.open(
Paths.get(targetPath),
StandardOpenOption.CREATE,
StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING)) {
// 使用transferTo进行高效的文件复制
source.transferTo(0, source.size(), target);
System.out.println("文件复制成功");
} catch (IOException e) {
e.printStackTrace();
}
}
}
2. 使用文件锁的读写示例
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class FileChannelWithLockDemo {
public static void main(String[] args) {
String filePath = "locked_file.txt";
// 线程1 - 写入数据
new Thread(() -> {
writeWithLock(filePath, "线程1写入的数据");
}).start();
// 线程2 - 尝试写入
new Thread(() -> {
writeWithLock(filePath, "线程2写入的数据");
}).start();
}
public static void writeWithLock(String filePath, String content) {
try (FileChannel channel = FileChannel.open(
Paths.get(filePath),
StandardOpenOption.CREATE,
StandardOpenOption.WRITE,
StandardOpenOption.APPEND)) {
// 尝试获取排他锁
System.out.println(Thread.currentThread().getName() + " 尝试获取文件锁...");
FileLock lock = channel.lock();
try {
System.out.println(Thread.currentThread().getName() + " 获取到文件锁");
// 模拟耗时操作
Thread.sleep(2000);
// 写入数据
ByteBuffer buffer = ByteBuffer.wrap((content + "\n").getBytes());
channel.write(buffer);
System.out.println(Thread.currentThread().getName() + " 写入完成");
} finally {
lock.release();
System.out.println(Thread.currentThread().getName() + " 释放文件锁");
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
3.内存映射文件示例
import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class MemoryMappedFileDemo {
public static void main(String[] args) {
String filePath = "mapped_file.txt";
// 写入内存映射文件
writeMappedFile(filePath, "这是内存映射文件的内容");
// 读取内存映射文件
readMappedFile(filePath);
}
public static void writeMappedFile(String filePath, String content) {
try (FileChannel channel = FileChannel.open(
Paths.get(filePath),
StandardOpenOption.READ,
StandardOpenOption.WRITE,
StandardOpenOption.CREATE)) {
// 创建内存映射
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_WRITE,
0,
content.getBytes().length);
// 直接操作内存
buffer.put(content.getBytes());
System.out.println("内存映射文件写入完成");
} catch (IOException e) {
e.printStackTrace();
}
}
public static void readMappedFile(String filePath) {
try (FileChannel channel = FileChannel.open(
Paths.get(filePath),
StandardOpenOption.READ)) {
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_ONLY,
0,
channel.size());
byte[] bytes = new byte[(int) channel.size()];
buffer.get(bytes);
System.out.println("读取到的内容: " + new String(bytes));
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileChannel的lock详解
一、文件锁的基本概念
文件锁分为两种类型:
-
排他锁(独占锁):
-
同一时间只能有一个进程持有
-
其他进程无法获取任何类型的锁
-
-
共享锁(读锁):
-
可以被多个进程同时持有
-
但只要有进程持有共享锁,就不能获取排他锁
-
二、lock()方法的使用
1. 基本锁定方式
FileChannel channel = FileChannel.open(Paths.get("test.txt"),
StandardOpenOption.READ, StandardOpenOption.WRITE);
// 获取排他锁(默认)
FileLock exclusiveLock = channel.lock();
// 获取共享锁(第三个参数为true)
FileLock sharedLock = channel.lock(0, Long.MAX_VALUE, true);
2. 方法参数说明
public final FileLock lock(long position, long size, boolean shared)
-
position
:锁定区域的起始位置 -
size
:锁定区域的大小(比特) -
shared
:是否为共享锁(true=共享锁,false=排他锁)
3.非阻塞尝试锁定
FileLock tryLock = channel.tryLock(); // 非阻塞版本
if (tryLock == null) {
// 获取锁失败
}
tryLock.release()
四、注意事项
-
锁的有效性:
-
文件锁是建议性的(advisory),不是强制性的
-
只有遵守锁协议的进程才会受锁影响
-
操作系统可能有不同的实现方式
-
-
性能考虑:
-
频繁获取/释放锁会影响性能
-
锁定大文件区域可能降低并发性
-
-
异常处理:
-
OverlappingFileLockException
:当请求的锁区域与现有锁重叠时抛出 -
NonWritableChannelException
:尝试在只读通道上获取排他锁
-
-
平台差异:
-
Windows系统上的实现与Unix-like系统不同
-
某些网络文件系统可能不支持文件锁
-
五、适用场景
-
多进程共享文件访问控制
-
防止文件被多个写入者同时修改
-
实现简单的进程间同步机制
-
保护关键配置文件不被并发修改
正确使用FileChannel的锁机制可以有效地管理对共享文件的并发访问,保证数据的一致性和完整性。