Java NIO之FileChannel 详解

关键点说明

  1. 文件打开选项

    • StandardOpenOption.CREATE - 文件不存在时创建

    • StandardOpenOption.READ/WRITE - 读写权限

    • StandardOpenOption.APPEND - 追加模式

    • StandardOpenOption.TRUNCATE_EXISTING - 清空已存在文件

  2. 缓冲区操作

    • ByteBuffer.wrap() 包装现有字节数组

    • buffer.flip() 切换读写模式

    • 直接操作缓冲区提高性能

  3. 高效文件复制

    • transferTo()/transferFrom() 方法比传统流复制更高效
  4. 资源管理

    • 使用try-with-resources确保通道自动关闭

    • 文件锁需要显式释放

这些示例展示了FileChannel的基本读写操作、文件锁的使用以及内存映射文件的高效操作,可以根据实际需求进行调整和扩展。

1. 基本文件读写示例

java 复制代码
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. 使用文件锁的读写示例

java 复制代码
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.内存映射文件示例

java 复制代码
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详解

一、文件锁的基本概念

文件锁分为两种类型:

  1. 排他锁(独占锁)

    • 同一时间只能有一个进程持有

    • 其他进程无法获取任何类型的锁

  2. 共享锁(读锁)

    • 可以被多个进程同时持有

    • 但只要有进程持有共享锁,就不能获取排他锁

二、lock()方法的使用

1. 基本锁定方式

java 复制代码
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. 方法参数说明

java 复制代码
public final FileLock lock(long position, long size, boolean shared)
  • position:锁定区域的起始位置

  • size:锁定区域的大小(比特)

  • shared:是否为共享锁(true=共享锁,false=排他锁)

3.非阻塞尝试锁定

java 复制代码
FileLock tryLock = channel.tryLock(); // 非阻塞版本
if (tryLock == null) {
    // 获取锁失败
}
tryLock.release()

四、注意事项

  1. 锁的有效性

    • 文件锁是建议性的(advisory),不是强制性的

    • 只有遵守锁协议的进程才会受锁影响

    • 操作系统可能有不同的实现方式

  2. 性能考虑

    • 频繁获取/释放锁会影响性能

    • 锁定大文件区域可能降低并发性

  3. 异常处理

    • OverlappingFileLockException:当请求的锁区域与现有锁重叠时抛出

    • NonWritableChannelException:尝试在只读通道上获取排他锁

  4. 平台差异

    • Windows系统上的实现与Unix-like系统不同

    • 某些网络文件系统可能不支持文件锁

五、适用场景

  1. 多进程共享文件访问控制

  2. 防止文件被多个写入者同时修改

  3. 实现简单的进程间同步机制

  4. 保护关键配置文件不被并发修改

正确使用FileChannel的锁机制可以有效地管理对共享文件的并发访问,保证数据的一致性和完整性。

相关推荐
okok__TXF31 分钟前
Mybatis源码分析
java·后端·mybatis
白云如幻1 小时前
【Java】Hibernate的一级缓存
java·hibernate
eternal__day1 小时前
Spring Boot 快速入手
java·spring boot·后端·spring·java-ee·maven
爱的叹息1 小时前
Spring Boot中事务状态(TransactionStatus)的核心信息及常见应用场景
java·spring boot·后端
杉之2 小时前
Java中的不可变集合
java·笔记·学习
潘多编程2 小时前
Gradle实战指南:从入门到进阶,与Maven的深度对比
java·maven
故城、2 小时前
MQ中的RabbitMQ
java·mq
橘猫云计算机设计2 小时前
基于JavaWeb的二手图书交易系统(源码+lw+部署文档+讲解),源码可白嫖!
java·开发语言·前端·毕业设计·php
猿java2 小时前
程序员,你使用过灰度发布吗?
java·分布式·后端