上一次聊了NIO基础中的三大组建和BetyBuffer的东西。
这次就聊文件编程 FileChannel
一、FileChannel
工作模式
FileChannel只能工作在阻塞模式下,因此它并不能配合Selector(选择器)来使用。
获取
不能直接打开FileChannel,必须通过FileInputStream、FileOutputStream
或者RandomAccessFile来获取FileChannel实例,它们都有getChannel()方法。
- 通过FileInputStream创建的channel只能读。
- 通过FileOutputStream创建的channel只能写。
- 通过RandomAccessFile创建的channel能否读写,根据构造 RandomAccessFile时的读写模式决定。
读取
FileChannel提供了read()方法来读取数据到缓冲区中。
int readIndex = fileChannel.read(byteBuffer);
read方法的返回值代表读取到的数据位置,当返回值等于“-1”时说明读取到了文件末尾处。可以凭此判断数据是否读取完毕了。
写入
FileChannel提供了 write 方法来写入数据到通道中。
但是因为channel的大小有限制,所以 write 方法并不能保证一次将缓冲区中的内容全部写入 channel。必须需要按照以下规则进行写入:
// 通过hasRemaining()方法查看缓冲区中是否还有数据未写入到通道中
while(buffer.hasRemaining()) {
channel.write(buffer);
}
关闭
channel使用结束后必须关闭,可以通过 close 方法进行通道的关闭。
最好使用下面的方法获取channel,避免因为一些意外导致资源无法正常关闭。
// 获取FileChannel
try (FileChannel channel = new FileInputStream("hello.txt").getChannel()) {
// 业务代码
}
} catch (IOException e) {
// ...
}
位置
通过channel.position()方法可以数据读取的位置。
long pos = channel.position();
可以传入一个long类型的值来设置channel中position的值。
long newPos = 1000
channel.position(newPos);
大小
通过channel.size()方法可以获取小大。
强制写入
操作系统出于性能考虑,会将数据先缓存,并不是立刻写入磁盘。可以调用 force(true) 方法将文件内容和元数据(文件的权限信息)立刻写入磁盘中。
但是该方法会影响性能。
二、两个Channel传输数据
transferTo方法
使用transferTo方法可以快速,高校的将一个channel中的数据传输给另一个channel(底层会零拷贝进行优化),但是一次最多只能传输2G的数据。
transferTo需要传入三个参数:
- position:传输的起始位置。
- count:传输的数据量。
- target:目标channel。
方法返回值代表已经传输的数据量。
public class FileChannelTransferToTest {
public static void main(String[] args) {
try (FileChannel inputChannel = new FileInputStream("hello.txt").getChannel();
FileChannel outputChannel = new FileInputStream("hello-out.txt").getChannel()) {
long transferToSize = inputChannel.transferTo(0, inputChannel.size(), outputChannel);
} catch (IOException e) {
e.printStackTrace();
}
}
}
当文件大小大于2G时如何处理?
public class FileChannelTransferToTest {
public static void main(String[] args) {
try (FileChannel inputChannel = new FileInputStream("hello.txt").getChannel();
FileChannel outputChannel = new FileInputStream("hello-out.txt").getChannel()) {
// 获取inputChannel的大小
long inputChannelSize = inputChannel.size();
// left代表剩余多少字节
for (long left = inputChannelSize; left > 0; ) {
left -= inputChannel.transferTo((inputChannelSize - left), left, outputChannel);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
三、Paths&Paths
- Path:文件路径。
- Paths:工具类,获取Path实例。
// 相对路径,使用usr.dir环境变量来定位1.txt
Path path1 = Paths.get("1.txt");
// path1 = 1.txt
System.out.println(path1);
// 绝对路径,代表 d:\1.txt
Path path2 = Paths.get("d:\\1.txt");
// path2 = d:\1.txt
System.out.println(path2);
// 绝对路径,代表 d:\1.txt
Path path3 = Paths.get("d:/1.txt");
// path3 = d:\1.txt
System.out.println(path3);
// 代表 d:\data\demo
Path path4 = Paths.get("d:\\data","demo");
// path4 = d:\data\demo
System.out.println(path4);
- ·:当前路径下
- ··:上一级路径下
例如现有目录如下
d:
|- data
|- demo
|- a
|- b
Path path5 = Paths.get("d:\\data\\demo\\a\\..\\b");
// path5 = d:\data\demo\a\..\b
System.out.println(path5);
// path5.normalize() = d:\data\demo\b
System.out.println(path5.normalize());
normalize方法会分析并返回最终的路径。
四、Files
检查(exists)
检测文件是否存在
// 检查文件是否存在
Path path = Paths.get("hello/1.txt");
boolean exists = Files.exists(path);
创建(createDirectory&createDirectories)
创建一级目录
- 如果目录已经存在,会抛出FileAlreadyExistsException异常。
- 不能创建多级目录,否则会抛出NoSuchFileException异常。
// 创建一个一级目录
Path path2 = Paths.get("hello/a");
// 如果目录已经存在,会抛异常:FileAlreadyExistsException
// 如果是创建多级目录,会抛异常:NoSuchFileException
Files.createDirectory(path2);
创建多级目录
// 创建多级目录
Path path3 = Paths.get("hello/a/a-1");
Files.createDirectories(path3);
拷贝(copy)
- 如果文件已经存在,会抛出FileAlreadyExistsException异常。
- 如果希望用source覆盖掉target,需要用StandardCopyOption来控制。
// 拷贝
Path path4 = Paths.get("hello/source.txt");
Path path5 = Paths.get("hello/target.txt");
Files.copy(path4,path5, StandardCopyOption.REPLACE_EXISTING);
移动(move)
// 拷贝
Path path4 = Paths.get("hello/source.txt");
Path path5 = Paths.get("hello/target.txt");
// StandardCopyOption.ATOMIC_MOVE保存文件移动的原子性
Files.move(path4,path5,StandardCopyOption.ATOMIC_MOVE);
删除(delete)
删除文件
// 删除文件
Path path4 = Paths.get("hello/source.txt");
Files.delete(path4);
如果文件不存在,会抛出NoSuchFileException异常。
删除文件夹
// 删除文件夹
Path path5 = Paths.get("hello/a");
Files.delete(path5);
如果文件夹里还存在内容,会抛出DirectoryNotEmptyException异常
遍历(walkFileTree)
walkFileTree方法需要传入两个参数:
-
Path:文件的路径。
-
FileVisitor:文件访问器。(访问者模式)
- 这里使用实现类SimpleFileVisitor,其有四个方法:
- preVisitDirectory:访问目录前的操作
- visitFile:访问文件的操作
- visitFileFailed:访问文件失败时的操作
- postVisitDirectory:访问目录后的操作
- 这里使用实现类SimpleFileVisitor,其有四个方法:
Files.walkFileTree(Paths.get("F:\\dev\\Java\\jdk1.8.0_131"), new SimpleFileVisitor<Path>(){
// 访问目录前的操作
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
return super.preVisitDirectory(dir, attrs);
}
// 访问文件的操作
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
return super.visitFile(file, attrs);
}
// 访问文件失败时的操作
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
return super.visitFileFailed(file, exc);
}
// 访问目录后的操作
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
return super.postVisitDirectory(dir, exc);
}
});