IO基础知识及示例整理(递归复制删除非空目录、模拟oj系统)

IO

基础知识

  • CPU只能与内存进行直接的数据交换,CPU和IO设备需通过内存作为中介进行数据交换。

  • 易失性存储:CPU(寄存器、三级缓存)、内存

    ​ —— 断电即丢失,以进程为单位进行管理

    持久性存储:硬盘、脱机存储(U盘、移动硬盘、磁带、光盘)、网络存储(网盘)

    ​ —— 断电不丢失,通常可以跨进程读写


    在这里插入图片描述

  • 变量、对象本质上都是编程语言对内存的抽象。

    文件是对硬盘中数据的抽象。

  • 内存 RAM(Random Access Memory)随机存取存储器,每个内存空间都有唯一的内存地址,内存寻址类似于根据下标访问数组,支持 O(1) 的时间复杂度(随机存取)。

  • 磁盘寻址需要旋转盘片找到对应的扇区和具体磁道,耗时长,故速度慢。
    磁盘更适合进行连续数据的读写操作,不适合随机读写。

  • 文件树结构只是一个逻辑结构,并不是硬盘上存储的实际物理结构。

  • 文件分为两大类:目录、普通文件。

    普通文件包含:文本文件(可读懂)、二进制文件(读不懂)。

    文本文件格式如:.txt .java
    二进制文件格式如:.png .class

  • 代码中的文件路径分隔符使用 \\/

  • 文件树中的结点分为 叶子结点 和 非叶子结点 。

    普通文件:叶子结点

    目录文件:非叶子结点、叶子结点(空目录)

  • 文件的移动:剪切粘贴、重命名

  • 移动、复制操作:
    普通文件 —— 对结点进行操作
    目录文件 —— 对整棵子树进行操作

  • 文件的删除:默认情况下只能删除结点,不能删除整棵子树(需逐个结点遍历删除)。

    ​ 即可删除普通文件、空目录,不可删除非空目录。

遍历删除非空目录

深度优先遍历文件树,判断是目录则继续递归删除,是文件则直接删除。

对于目录的递归删除:先递归后删除

DeleteTraversal(file);
file.delete();

递归代码会未到达底层时会卡在该行,直到底层才会向下执行删除代码,此时将叶子结点的空目录删除,返回上层,此时上层的非空目录也变为了空目录,上层也得以向下执行删除代码,删除空目录,以此逐层向上“归”实现整棵树的删除。

public class TraversalTest {

    public static void main(String[] args) throws Exception {
        File file = new File("D:\\Learn\\Bit\\ee\\iotest");
        System.out.println(file.delete());  //输出false,无法删除非空目录
        DeleteTraversal(file);  //递归删除iotest目录下的所有文件
        System.out.println(file.delete());  //输出true,删除iotest目录本身(此时已为空目录)
    }

    /**
     * 深度优先遍历删除非空目录
     * @param dir 目录对应的根结点
     */
    public static void DeleteTraversal(File dir) throws IOException {
        //当前目录的下级孩子结点(不包括孙子结点)
        File[] files = dir.listFiles();
        //遍历每个孩子结点,若是非空目录则继续递归,若是空目录、普通文件则删除
        for(File file : files) {
            //是目录,需继续递归删除
            if(file.isDirectory()) {
                // 去除文件路径中无意义的./和../    第一个反斜杠为转义字符,输出一个反斜杠(区分目录和文件)
                System.out.println(file.getCanonicalPath() + "\\");
                //继续递归遍历此孩子目录
                DeleteTraversal(file);
                //当遍历结束即递归到底时,此时目录一定为空目录,可删除
                file.delete();
                /* 删除代码置于递归代码后方的原因:
                   删除最底层的空目录后返回到上一层的递归代码处,此时上一层的非空目录也已经变为空目录,继续向下执行删除代码,删除空目录,逐层向上“归”实现整棵树的删除
                 */
            }
            //是文件,直接删除
            if(file.isFile()) {
                System.out.println(file.getCanonicalPath());
                file.delete();
            }
        }
    }
}

测试结果:

delete()和deleteOnExit()

delete()方法:立即删除文件,但只能删除叶子结点(普通文件、空目录),不能删除非空目录。

true:删除成功

false:要删除的文件不存在 / 删除的是非空目录

IOException:其他异常情况,如文件正在被其他进程打开使用

deleteOnExit()方法:不立即删除文件,待到 JVM 退出时才真正删除。

  • delete()方法的删除不进入回收站,直接删除无法找回。

    Windows系统进入回收站的删除,只是将删除的文件剪切到一个固定目录下。

File方法总结

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

try-with-resources 语法糖

语法:try(定义变量){}
属于语法糖,编译后自动添加close()释放资源的代码,帮助程序员简化代码书写。

字符集

UTF-8为变长的编码规则,一个字符占用的空间为1~4字节不等。

ASCII码范围内的字符仍然占用一个字节。中文字符一般占用3个字节。

简单示例

查找符合条件的文件并删除
public class FindAndDelete {

    public static void main(String[] args) throws IOException {

        // 1.先遍历文件树,查找所有符合条件的文件,存入List<File>中
        String rootPath = "D:\\Learn\\Bit\\ee\\iotest";  //根目录路径
        String condition = ".doc";  //文件符合的条件
        List<File> resultList = new ArrayList<>();  //结果列表
        //遍历根目录下的整棵树,将符合条件的文件存入列表
        File rootFile = new File(rootPath);  //根目录结点
        traversal(rootFile, condition, resultList);

        // 2.遍历符合条件的文件列表,逐个询问用户是否要删除
        Scanner sc = new Scanner(System.in);
        for(File file : resultList) {
            System.out.println("是否要删除该文件:" + file.getCanonicalPath() + " (true/false)");
            //若用户输入错误,则退出
            if(!sc.hasNextBoolean()) {
                System.out.println("输出有误,程序退出");
                return;
            }
            //用户输入正确
            if(sc.nextBoolean()) {
                file.delete();  //立即删除且无法找回
            }
        }
    }

    /**
     * 深度优先遍历,查找根目录下所有符合指定条件的文件,存入结果列表中
     * @param dirFile 根目录结点
     * @param condition 符合的指定条件
     * @param resultList 结果列表
     */
    private static void traversal(File dirFile, String condition, List<File> resultList) throws IOException {
        //获取当前目录下层的所有孩子结点(不包括孙子结点)
        File[] files = dirFile.listFiles();
        //若没有孩子结点(为空目录),则不进行遍历
        if(files == null) {
            return;
        }
        for(File file : files) {
            //若是目录,则继续递归遍历
            if(file.isDirectory()) {
                traversal(file, condition, resultList);
            } else
            //若是文件,则判断是否符合条件,若符合条件则存入结果列表
            if(file.isFile()) {
                if(file.getName().endsWith(condition)) {
                    System.out.println(file.getCanonicalPath());
                    resultList.add(file);
                }
            }
        }
    }
}
复制单个普通文件
public class CopyFile {
    public static void main(String[] args) throws Exception {

        File srcFile = new File("D:\\Learn\\Bit\\ee\\copyjpg\\src.jpg");
        File destFile = new File("D:\\Learn\\Bit\\ee\\copyjpg\\dest.jpg");

        long startedAt = System.currentTimeMillis();
        int count = 0;
        //边读边写,从is读出的写入到os中,一次读一个字节数组的数据
        byte[] buf = new byte[1024];
        try(InputStream is = new FileInputStream(srcFile)) {  //读取源文件的输入流
            try(OutputStream os = new FileOutputStream(destFile)) {  //写到目标文件的输出流
                int len;
                while((len = is.read(buf)) != -1) {
                    os.write(buf, 0, len);
                    count += len;
                    System.out.printf("已经复制了 %d 字节的数据\n", count);
                }
                //冲刷缓冲区
                os.flush();
            }
        }
        long endedAt = System.currentTimeMillis();
        System.out.printf("复制共消耗了: %.3f 秒\n", (endedAt - startedAt) / 1000.0);  //时间戳的单位为ms
    }
}
复制非空目录

目标目录destdirectory必须已存在,否则new FileOutputStream()会抛出找不到文件FileNotFoundException异常。

递归遍历非空目录,若遇到子目录则继续递归,遇到文件则逐一复制。

public class CopyDirectory {

    //定义成static属性,供类中所有方法使用
    static File srcDirectory = new File("D:\\Learn\\Bit\\ee\\copydirectory\\srcdirectory");
    static File destDirectory = new File("D:\\Learn\\Bit\\ee\\copydirectory\\destdirectory");  //此destdirectory必须已存在
    static String srcDirectoryPath;
    static String destDirectoryPath;

    static {
        try {
            srcDirectoryPath = srcDirectory.getCanonicalPath();
            destDirectoryPath = destDirectory.getCanonicalPath();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) throws Exception {
        //遍历源目录,将其中的每个文件都逐一复制到目标目录中的相应位置(需求得相对路径)
        traversal(srcDirectory);
    }

    private static void traversal(File srcDirectory) throws Exception {
        File[] srcFiles = srcDirectory.listFiles();
        if(srcFiles == null) {
            return;
        }
        for(File srcFile : srcFiles) {
            //从源文件路径中截取相对路径
            String srcFilePath = srcFile.getCanonicalPath();
            String relativePath = srcFilePath.substring(srcDirectoryPath.length());  //去除根目录的路径即为相对路径
            //拼接相对路径到目标文件路径中
            String destFilePath = destDirectoryPath + relativePath;
            System.out.println(destFilePath);
            //创建复制后的目标文件
            File destFile = new File(destFilePath);
            //判断源文件是否是目录,若为目录则先创建目标目录,再递归遍历源目录,复制其下的文件
            if (srcFile.isDirectory()) {
                //是目录,先创建目标目录
                destFile.mkdir();    //保证其父目录一定存在
                //继续递归源目录
                traversal(srcFile);
            } else if (srcFile.isFile()) {
                copyFile(srcFile, destFile);
            }
        }
    }

    private static void copyFile(File srcFile, File destFile) throws Exception {
        long startedAt = System.currentTimeMillis();
        int count = 0;
        //边读边写,从is读出的写入到os中,一次读一个字节数组的数据
        byte[] buf = new byte[1024];
        try(InputStream is = new FileInputStream(srcFile)) {  //读取源文件的输入流
            try(OutputStream os = new FileOutputStream(destFile)) {  //写到目标文件的输出流
                int len;
                while((len = is.read(buf)) != -1) {
                    os.write(buf,0, len);
                    count += len;
                    System.out.printf("已经复制了 %d 字节的数据\n", count);
                }
                //冲刷缓冲区
                os.flush();
            }
        }
        long endedAt = System.currentTimeMillis();
        System.out.printf("复制共消耗了: %.3f 秒\n", (endedAt - startedAt) / 1000.0);  //时间戳的单位为ms
    }
}
模拟 OJ 系统

OJ 测试用例文件:

输入:input.txt

期望输出:output.txt


Solution.java 模拟用户写的功能代码:

public class Solution {
    //链表倒置
    public ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode cur = head;
        while (cur != null) {
            ListNode next = cur.next;
            //反转链表
            cur.next = prev;
            prev = cur;
            cur = next;
        }
//        return new ListNode(0);  //模拟solution未实现功能(实际输出与期望输出不同)
//        int num = 2/0;  //模拟solution执行过程中发生的异常
        return prev;
    }
}

//链表结点
class ListNode {
    public int val;
    public ListNode next;
    public ListNode(int val) {
        this.val = val;
    }
}

Test.java 模拟系统后台的测试代码:

public class Test {

    public static void main(String[] args) throws Exception {

        //测试用例的输入输出文件
        File inputFile = new File("D:\\Learn\\Bit\\ee\\oj\\input.txt");
        File outputFile = new File("D:\\Learn\\Bit\\ee\\oj\\output.txt");

        try(InputStream isInput = new FileInputStream(inputFile)) {
            try(InputStream isOutput = new FileInputStream(outputFile)) {
                //包装成Scanner类,可以调用nextLine()方法一次读取一行,逐行进行链表倒置
                try(Scanner scInput = new Scanner(isInput, StandardCharsets.UTF_8)) {
                    try(Scanner scOutput = new Scanner(isOutput, StandardCharsets.UTF_8)) {
                        //循环逐行读取,对每行的链表倒置进行测试
                        while(scInput.hasNextLine()) {
                            String inputLine = scInput.nextLine();
                            String outputLine = scOutput.nextLine();
                            //将inputLine根据solution的规则进行链表倒置后的结果与outputLine比对是否相同
                            test(inputLine, outputLine);
                        }
                    }
                }
            }
        }
        System.out.println("测试用例全部通过");
    }

    private static void test(String inputLine, String outputLine) {

        // 1.将inputLine根据solution的规则进行链表倒置,得到实际输出的结果actualLine
        ListNode dummyHead = new ListNode(-1);  //虚拟头结点,-1表示倒置后的结尾
        ListNode tail = dummyHead;
        // 1) 将inputLine通过不断尾插构建成链表
        String[] inputs = inputLine.split(" ");  //以空格分隔,取出每个字符转为数字
        for(String input : inputs) {
            //尾插入链表
            ListNode node = new ListNode(Integer.parseInt(input));
            tail.next = node;  //原尾结点指向此新结点
            tail = node;  //新结点成为尾结点
        }
        // 2) 对虚拟头结点后方链表进行倒置,虚拟头结点是额外给inputLine添加的,倒置时需排除,否则就比outputLine多了一个-1
        Solution solution = new Solution();
        try {  //捕获solution遇到的异常情况
            ListNode reverseHead = solution.reverseList(dummyHead.next);
            // 3) 将倒置后的链表转为actualLine字符串
            StringBuilder actualLine = new StringBuilder();
            //遍历链表逐个拼接
            for(ListNode cur = reverseHead; cur != null; cur = cur.next) {
                actualLine.append(cur.val).append(" ");
            }
            //删除字符串中最后一个多余的空格
            if(actualLine.length() > 0) {  //删除前先判空
                actualLine.delete(actualLine.length()-1, actualLine.length());
            }
            // 2.将实际输出actualLine与期望输出outputLine进行比对
            if(!actualLine.toString().equals(outputLine)) {
                System.out.println("输入:" +inputLine);
                System.out.println("期望输出:" + outputLine);
                System.out.println("实际输出:" + actualLine);
                System.exit(1);  //solution正常执行但功能未实现,正常退出
            }
        }catch (Exception e) {
            System.out.println("输入:" +inputLine);
            System.out.println("期望输出:" + outputLine);
            System.out.println("实际输出:");  //空
            throw new RuntimeException(e);  //solution在执行过程中抛出异常,异常退出
        }
    }
}

solution执行成功,功能已实现(实际输出与期望输出相同),显示通过测试用例。

solution执行成功,功能未实现(实际输出与期望输出不同),显示错误的实际输出。

solution在执行过程中抛出异常,显示实际输出为空,并打印异常信息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0tXsGDki-1657494434669)(C:/Users/Hong/AppData/Roaming/Typora/typora-user-images/image-20220708105952523.png)]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值