• 02 Java文件读写通道的使用以及文件的基本操作方法


    一、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类读取指定的文件。

    Interface Path

    Class 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);
    

    参考资料

    01 FileChannel的官方API文档

    02 Netty基础课程

  • 相关阅读:
    AT+CNMI的使用
    oracle 触发器及游标的使用
    索引优缺点
    SQL Server里的主键、唯一性约束、聚集索引
    C++中使用内存映射文件处理大文件
    oracle 学习笔记
    香干炒肉丝
    如何将数据导入到 SQL Server Compact Edition 数据库中
    解决ORACLE密码遗忘
    Office 2003 主 Interop 程序集的安装和使用
  • 原文地址:https://www.cnblogs.com/kfcuj/p/14707966.html
Copyright © 2020-2023  润新知