上一次聊了NIO基础中的三大组建和BetyBuffer的东西。
这次就聊文件编程 FileChannel
FileChannel只能工作在阻塞模式下,因此它并不能配合Selector(选择器)来使用。
不能直接打开FileChannel,必须通过FileInputStream、FileOutputStream
或者RandomAccessFile来获取FileChannel实例,它们都有getChannel()方法。
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) 方法将文件内容和元数据(文件的权限信息)立刻写入磁盘中。
但是该方法会影响性能。
使用transferTo方法可以快速,高校的将一个channel中的数据传输给另一个channel(底层会零拷贝进行优化),但是一次最多只能传输2G的数据。
transferTo需要传入三个参数:
方法返回值代表已经传输的数据量。
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(); } } }
// 相对路径,使用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方法会分析并返回最终的路径。
检测文件是否存在
// 检查文件是否存在 Path path = Paths.get("hello/1.txt"); boolean exists = Files.exists(path);
// 创建一个一级目录 Path path2 = Paths.get("hello/a"); // 如果目录已经存在,会抛异常:FileAlreadyExistsException // 如果是创建多级目录,会抛异常:NoSuchFileException Files.createDirectory(path2);
// 创建多级目录 Path path3 = Paths.get("hello/a/a-1"); Files.createDirectories(path3);
// 拷贝 Path path4 = Paths.get("hello/source.txt"); Path path5 = Paths.get("hello/target.txt"); Files.copy(path4,path5, StandardCopyOption.REPLACE_EXISTING);
// 拷贝 Path path4 = Paths.get("hello/source.txt"); Path path5 = Paths.get("hello/target.txt"); // StandardCopyOption.ATOMIC_MOVE保存文件移动的原子性 Files.move(path4,path5,StandardCopyOption.ATOMIC_MOVE);
// 删除文件 Path path4 = Paths.get("hello/source.txt"); Files.delete(path4);
如果文件不存在,会抛出NoSuchFileException异常。
// 删除文件夹 Path path5 = Paths.get("hello/a"); Files.delete(path5);
如果文件夹里还存在内容,会抛出DirectoryNotEmptyException异常
walkFileTree方法需要传入两个参数:
Path:文件的路径。
FileVisitor:文件访问器。(访问者模式)
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); } });