NIO基础——文件编程

上一次聊了NIO基础中的三大组建和BetyBuffer的东西。
这次就聊文件编程 FileChannel

一、FileChannel

工作模式

FileChannel只能工作在阻塞模式下,因此它并不能配合Selector(选择器)来使用。

获取

不能直接打开FileChannel,必须通过FileInputStreamFileOutputStream
或者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:访问目录后的操作
        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);
            }
        });

本篇文章根据在学习《黑马程序员Netty实战》时所做的笔记总结。

上一篇:实时语义分割模型——BiSeNet


下一篇:RK3399 Android8.1 息屏不休眠修改