目录
一、FileChannel基本使用方法
1-1-1 概述
A channel for reading, writing, mapping, and manipulating a file.A file channel is a SeekableByteChannel that is connected to a file. It has a current position within its file which can be both queried and modified.
特点:FileChannel 只能工作在阻塞模式下,网络编程中socket channel配合selector可以工作在非阻塞模式下。
- Java中程序对于文件的字节操作需要借助文件读写通道,实际应用时,channel与buffer配合使用实现文件的修改
1-1-2 FileChannel基本使用方法
FileChannel获取的三种方式
不能直接打开 FileChannel,必须通过 FileInputStream、FileOutputStream 或者 RandomAccessFile 来获取 FileChannel,它们都有 getChannel 方法
1)通过 FileInputStream 获取的 channel 只能读
2)通过 FileOutputStream 获取的 channel 只能写
3)通过 RandomAccessFile 是否能读写根据构造 RandomAccessFile 时的读写模式决定
读取
/*channel 读取数据填充 ByteBuffer,返回值表示读到了多少字节,-1 表示到达了文件的末尾*/
int readBytes = channel.read(buffer);
写入
注意点:每次写入前对buffer进行检查,看有没有数据,如果有数据应先写入剩余数据。
- 主要原因在于buffer有可能不是一次写入。
ByteBuffer buffer = ...;
buffer.put(...); // 存入数据
buffer.flip(); // 切换读模式
while(buffer.hasRemaining()) {
channel.write(buffer);
}
关闭
channel 必须关闭,不过调用了 FileInputStream、FileOutputStream 或者 RandomAccessFile 的 close 方法会间接地调用 channel 的 close 方法
数据位置获取
- 获取当前位置
long pos = channel.position();
- 设置当前位置
long newPos = ...;
channel.position(newPos);
- 设置当前位置时,如果设置为文件的末尾
- 这时读取会返回 -1
- 这时写入,会追加内容,但要注意如果 position 超过了文件末尾,再写入时在新内容和原末尾之间会有空洞(00)
大小
- 使用 size 方法获取文件的大小
强制写入(注意点)
force(true)
- 操作系统出于性能的考虑,会将数据缓存,不是立刻写入磁盘。
- 调用 force(true) 方法将文件内容和元数据(文件的权限等信息)立刻写入磁盘
1-1-3 FileChannel应用:传输数据
transferTo:通过零拷贝技术将一个文件的内容拷贝到另外一个文件中。
Transfers bytes from this channel's file to the given writable byte channel.
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
public class test7 {
public static void main(String[] args) {
String FROM = "words.txt";
String TO = "to.txt";
long start = System.nanoTime();
try (FileChannel from = new FileInputStream(FROM).getChannel();
FileChannel to = new FileOutputStream(TO).getChannel();
) {
/*起始位置,数据大小,目标读写通道*/
/*transferTo:底层使用零拷贝技术,效率比较高*/
/*注意点:这个方法调用一次传输的文件大小是有上限的,大约2G左右,多余的不会传送,因此文件太大需要分多次传送*/
// from.transferTo(0, from.size(), to); // 将words.txt的内容拷贝到to.txt中。
/*通过循环确保大文件多次传输完成*/
long size = from.size();
for(long left = size;left > 0;) {
left -= from.transferTo(size - left, from.size(), to);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意点:
- 较大的文件需要考虑多次传输问题。
二、Paths配合Files工具类的基本使用
2-1 概述
Paths类作用:jdk7 引入了 Path 和 Paths 类,Path 用来表示文件路径,Paths 是工具类,用来获取 Path 实例,Path配合Paths类使用。
Files类作用:配合Paths类读取指定的文件。
2-2 Paths的使用示例
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:dataprojects
示例
import java.nio.file.Path;
import java.nio.file.Paths;
public class test8 {
public static void main(String[] args) {
Path path = Paths.get("d:\data\projects\a\..\b");
System.out.println(path);
System.out.println(path.normalize()); // 正常化路径
}
}
执行结果
- 能够对..和.进行解析
d:dataprojectsa..
d:dataprojects
2-3 Files的使用
2-3-1 基本使用
检查文件是否存在
Path path = Paths.get("helloword/data.txt");
System.out.println(Files.exists(path));
创建一级目录
Path path = Paths.get("helloword/d1");
Files.createDirectory(path);
- 如果目录已存在,会抛异常 FileAlreadyExistsException
- 不能一次创建多级目录,否则会抛异常 NoSuchFileException
创建多级目录
Path path = Paths.get("helloword/d1/d2");
Files.createDirectories(path);
拷贝文件
Path source = Paths.get("helloword/data.txt");
Path target = Paths.get("helloword/target.txt");
Files.copy(source, target);
- 如果文件已存在,会抛异常 FileAlreadyExistsException
如果希望用 source 覆盖掉 target,需要用 StandardCopyOption 来控制
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
移动文件
Path source = Paths.get("helloword/data.txt");
Path target = Paths.get("helloword/data.txt");
Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);
- StandardCopyOption.ATOMIC_MOVE 保证文件移动的原子性
删除文件
Path target = Paths.get("helloword/target.txt");
Files.delete(target);
- 如果文件不存在,会抛异常 NoSuchFileException
删除目录
Path target = Paths.get("helloword/d1");
Files.delete(target);
- 目录还有内容,会抛异常 DirectoryNotEmptyException
2-3-2 遍历目录文件(访问者模式的实例)
需求:统计某个目录下有多少文件,目录以及jar包。
package part1;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.concurrent.atomic.AtomicInteger;
public class test9 {
public static void main(String[] args) {
// 这里采用原子累加器的原因: 匿名内部类访问的局部变量,这个局部变量必须被final修饰,为了保证数据一致性,
// 所以普通局部变量无法在这里进行累加
// 在JDK1.8前,不加无法通过编译,JDK1.8字节码生成的时候会自动加上
AtomicInteger dirCount = new AtomicInteger();
AtomicInteger fileCount = new AtomicInteger();
AtomicInteger jarCount = new AtomicInteger();
try { /*起始目录,用于遍历文件的实例*/
Files.walkFileTree(Paths.get("C:\Program Files\Java\jdk1.8.0_131"), new SimpleFileVisitor<Path>(){
// 访问目录前调用:统计目录个数
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
// System.out.println(dir);
dirCount.incrementAndGet();
return super.preVisitDirectory(dir, attrs);
}
// 访问目录时调用:统计文件个数
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
// System.out.println(file);
fileCount.incrementAndGet();
if (file.toFile().getName().endsWith(".jar")) {
jarCount.incrementAndGet();
}
return super.visitFile(file, attrs);
}
});
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("Number of directory:"+dirCount);
System.out.println("Number of directory:"+fileCount);
System.out.println("Number of jar:"+jarCount);
}
}
执行结果
Number of directory:134
Number of directory:1476
上面代码的注意点:
- 匿名内部类中不能采用局部变量进行累加
- 上面的将对目录与文件的操作交给访问者类,是设计模式中的访问者模式
介绍
意图:主要将数据结构与数据操作分离。
主要解决:稳定的数据结构和易变的操作耦合问题。
何时使用:需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,使用访问者模式将这些封装到类中。
如何解决:在被访问的类里面加一个对外提供接待访问者的接口。
关键代码:在数据基础类里面有一个方法接受访问者,将自身引用传入访问者。
应用实例:您在朋友家做客,您是访问者,朋友接受您的访问,您通过朋友的描述,然后对朋友的描述做出一个判断,这就是访问者模式。
优点: 1、符合单一职责原则。 2、优秀的扩展性。 3、灵活性。
缺点: 1、具体元素对访问者公布细节,违反了迪米特原则。 2、具体元素变更比较困难。 3、违反了依赖倒置原则,依赖了具体类,没有依赖抽象。
使用场景: 1、对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。 2、需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。
注意事项:访问者可以对功能进行统一,可以做报表、UI、拦截器与过滤器。
2-3-3 删除多级目录
- 删除是危险操作,确保要递归删除的文件夹没有重要内容
Path path = Paths.get("d:\a");
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);
}
});
2-3-4 拷贝多级目录
long start = System.currentTimeMillis();
String source = "D:\Snipaste-1.16.2-x64";
String target = "D:\Snipaste-1.16.2-x64aaa";
Files.walk(Paths.get(source)).forEach(path -> {
try {
String targetName = path.toString().replace(source, target);
// 是目录
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();
}
});
long end = System.currentTimeMillis();
System.out.println(end - start);