切换主题
三、文件编程
一、FileChannel
FileChannel 仅支持阻塞模式,不支持非阻塞模式(区别于 SocketChannel/DatagramChannel);
1、获取
FileInputStream.getChannel()→ 仅读(Read-only),调用写入方法会抛异常;FileOutputStream.getChannel()→ 仅写(Write-only),调用读取方法会抛异常;RandomAccessFile.getChannel()→ 读写权限由创建 RandomAccessFile 时的模式参数决定(r= 只读,rw= 读写,rws/rwd= 带同步的读写)。
java
// 1.1 FileInputStream → 仅读Channel
try (FileInputStream fis = new FileInputStream(FILE_PATH);
FileChannel readOnlyChannel = fis.getChannel()) {
System.out.println("✅ FileInputStream获取的Channel:仅读");
}
// 1.2 FileOutputStream → 仅写Channel
try (FileOutputStream fos = new FileOutputStream(FILE_PATH);
FileChannel writeOnlyChannel = fos.getChannel()) {
System.out.println("✅ FileOutputStream获取的Channel:仅写");
}
// 1.3 RandomAccessFile → 读写Channel(rw模式)
try (RandomAccessFile raf = new RandomAccessFile(FILE_PATH, "rw");
FileChannel rwChannel = raf.getChannel()) {
System.out.println("✅ RandomAccessFile(rw)获取的Channel:读写");
}2、读取
会从 channel 读取数据填充 ByteBuffer,返回值表示读到了多少字节,-1 表示到达了文件的末尾
java
int readBytes = channel.read(buffer);3、写入
- ✅ 关键坑点:
write()方法不保证一次性写入所有数据(受缓冲区 / 系统缓存影响),可能出现「部分写入」; - ✅ 正确姿势:必须在
while循环中调用write(),结合buffer.hasRemaining()判断,确保缓冲区数据全部写入;
java
ByteBuffer buffer = ...;
buffer.put(...); // 存入数据
buffer.flip(); // 切换读模式
while(buffer.hasRemaining()) {
channel.write(buffer);
}4、关闭
- FileChannel 是资源型对象,必须关闭,否则会造成文件句柄泄漏;
- ✅ 便捷方式:调用
FileInputStream/FileOutputStream/RandomAccessFile的close()方法,会间接调用对应 Channel 的close()方法(推荐,无需手动关 Channel); - ✅ 最佳实践:使用 try-with-resources 语法(JDK7+),自动关闭资源,无需手动调用 close。
5、位置
获取当前位置
java
long pos = channel.position();设置当前位置
java
long newPos = ...;
channel.position(newPos);设置当前位置时,如果设置为文件的末尾
- 这时读取会返回 -1
- 这时写入,会追加内容,但要注意如果 position 超过了文件末尾,再写入时在新内容和原末尾之间会有空洞(00)
6、文件大小获取
- 方法:
long size()→ 返回文件的实际字节大小,精准可靠;
java
long fileSize = channel.size();7、强制写入
底层原理:操作系统为提升性能,会将写入数据缓存到内存,并非立刻刷写到磁盘,断电 / 宕机可能导致数据丢失;
方法:
javavoid force(boolean metaData) // 强制将内存中的数据刷写到磁盘; //true , 刷写「文件内容 + 文件元数据」(权限、修改时间等); //false,仅刷写「文件内容」;
8、传输数据
(1)小于2G
示例代码
java
public class TestFileChannel {
public static void main(String[] args) {
try (
FileChannel from = new FileInputStream("word.txt").getChannel();
FileChannel to = new FileOutputStream("word2.txt").getChannel();
) {
from.transferTo(0, from.size(), to);
} catch (IOException e) {
e.printStackTrace();
}
}
}(2)大于2G
示例代码
java
public class TestFileChannel1 {
public static void main(String[] args) {
try (
FileChannel from = new FileInputStream("word.txt").getChannel();
FileChannel to = new FileOutputStream("word3.txt").getChannel();
) {
long size = from.size();
for (long left = size; left > 0; ) {
left -= from.transferTo(size - left, left, to);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}二、Path
jdk7 引入了 Path 和 Paths 类
- Path 用来表示文件路径
- Paths 是工具类,用来获取 Path 实例
java
Path source = Paths.get("1.txt"); // 相对路径 使用 user.dir 环境变量来定位 1.txt
Path source = Paths.get("d:\\1.txt"); // 绝对路径 代表了 d:\1.txt
Path source = Paths.get("d:/1.txt"); // 绝对路径 同样代表了 d:\1.txt
Path projects = Paths.get("d:\\data", "projects"); // 代表了 d:\data\projects.代表了当前路径..代表了上一级路径
例如目录结构如下
d:
|- data
|- projects
|- a
|- b代码示例
java
Path path = Paths.get("d:\\data\\projects\\a\\..\\b");
System.out.println(path);
System.out.println(path.normalize()); // 正常化路径会输出
d:\data\projects\a\..\b
d:\data\projects\b三、Files
1、检查文件是否存在
java
Path path = Paths.get("helloword/data.txt");
System.out.println(Files.exists(path));2、创建一级目录
java
Path path = Paths.get("helloword/d1");
Files.createDirectory(path);- 如果目录已存在,会抛异常 FileAlreadyExistsException
- 不能一次创建多级目录,否则会抛异常 NoSuchFileException
3、创建多级目录用
java
Path path = Paths.get("helloword/d1/d2");
Files.createDirectories(path);4、拷贝文件
java
Path source = Paths.get("helloword/data.txt");
Path target = Paths.get("helloword/target.txt");
Files.copy(source, target);- 如果文件已存在,会抛异常 FileAlreadyExistsException
如果希望用 source 覆盖掉 target,需要用 StandardCopyOption 来控制
java
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);5、移动文件
java
Path source = Paths.get("helloword/data.txt");
Path target = Paths.get("helloword/data.txt");
Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);- StandardCopyOption.ATOMIC_MOVE 保证文件移动的原子性
6、删除文件
java
Path target = Paths.get("helloword/target.txt");
Files.delete(target);- 如果文件不存在,会抛异常 NoSuchFileException
7、删除目录
java
Path target = Paths.get("helloword/d1");
Files.delete(target);- 如果目录还有内容,会抛异常 DirectoryNotEmptyException
四、示例
1、查询目录的树形结构
java
public class TestFilesAndPath {
public static void main(String[] args) throws IOException {
Path path = Paths.get("C:\\study\\code\\blog");
AtomicInteger dirInteger = new AtomicInteger();
AtomicInteger fileInteger = new AtomicInteger();
Files.walkFileTree(path, new SimpleFileVisitor<Path>(){
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
System.out.println("目录" + dir);
dirInteger.incrementAndGet();
return super.preVisitDirectory(dir, attrs);
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
System.out.println("文件" + file);
fileInteger.incrementAndGet();
return super.visitFile(file, attrs);
}
});
System.out.println(dirInteger.get());
System.out.println(fileInteger.get());
}
}2、查询目录中特定文件
java
public class TestFilesAndPath {
public static void main(String[] args) throws IOException {
Path path = Paths.get("C:\\study\\code\\blog");
AtomicInteger fileInteger = new AtomicInteger();
Files.walkFileTree(path, new SimpleFileVisitor<Path>(){
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if(file.getFileName().toString().endsWith(".md")){
System.out.println("文件" + file);
fileInteger.incrementAndGet();
}
return super.visitFile(file, attrs);
}
});
System.out.println(fileInteger.get());
}
}3、删除树形目录
java
public class TestFilesAndPath {
public static void main(String[] args) throws IOException {
Path path = Paths.get("C:\\study\\code\\blog");
AtomicInteger fileInteger = new AtomicInteger();
Files.walkFileTree(path, new SimpleFileVisitor<Path>(){
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return super.visitFile(file, attrs);
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return super.postVisitDirectory(dir, exc);
}
});
System.out.println(fileInteger.get());
}
}4、拷贝多级目录
java
public class TestFilesAndPath {
public static void main(String[] args) throws IOException {
String source = "C:\\work\\作业一";
String target = "C:\\caogao\\a";
Files.walk(Paths.get(source)).forEach(path -> {
try {
String targetName = path.toString().replace(source, target);
System.out.println(targetName);
// 如果是目录
if (Files.isDirectory(path)) {
Files.createDirectory(Paths.get(targetName));
} else if (Files.isRegularFile(path)) {
Files.copy(path, Paths.get(targetName));
}
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
DQ博客