文件认识及操作(读写文件)

目录

一.文件

1.什么是文件

2.目录结构(基本常识)

二.File类的用法

1.File类

2.读文件和写文件

3.练习1--扫描指定目录

4.练习2--普通文件的复制

一.文件

1.什么是文件

  • 狭义上的文件:存储在硬盘上的数据,以“文件”为单位进行组织,常见的文件比如文本文件、图片、音频、可执行程序等。文件夹(目录)也是一种特殊的文件。硬盘的特点(相较于内存):存储空间大;访问速度慢;成本较低;断电数据不会丢失,持续化存储。
  • 广义上的文件:操作系统负责管理软硬件资源,操作系统会将这些资源抽象成“文件”来管理。例如从键盘读取数据就是把键盘抽象成文件,读这个文件就能读键盘输入的内容。

2.目录结构(基本常识)

  • 我们可以看到我们电脑文件夹是一层一层递进,且上层文件夹里可能会包含多个文件夹,这样就构成了一个树形结构。
  • 那我们如何描述我们的文件目录呢,通过“路径”就可以描述文件在电脑中的位置。路径分为绝对路径和相对路径。
  • 描述一个文件所在位置叫做绝对路径:E:\IntelliJ IDEA 2021.2\bin\idea64.exe,每个\分割的部分都是目录。
  • 相对路径:以基准路径(工作路径)为起点,怎么继续往下走才能找到目标路径,也就是找“参考系”。比如你要去一个陌生的地方,但是需要问路,每到一个地方问一次,那么每次这条路的起点是你脚下的地方,而不是你最开始问路的地方。这就叫做基准路径不同。
  • 以上述绝对路径为例,当基准路径是E:此时的相对路径就是./IntelliJ IDEA 2021.2/bin/idea64.exe,如果基准路径是E:/IntelliJ IDEA 2021.2,此时的相对路径就是./bin/idea54.exe……以此类推,就是相对路径的描述方式。如果基准路径是其他文件夹,那么就需要使用 .. 返回上级目录,直到返回要找的文件所在的目录。找工作目录比较复杂,不同的情况下工作目录所在的位置不一定是固定的。

二.File类的用法

1.File类

  • File类:这个类在java.io包中,IO就是输入输出。
  • File类中有很多方法供我们使用,包括返回 File 对象的父目录文件路径、返回 FIle 对象的纯文件名称 返回 File 对象的文件路径 返回 File 对象的绝对路径 等。其中File(String pathname)构造方法就能让我们创建对象时指定一个路径。
  • 构建File对象:
import java.io.IOException;

//观察 get 系列的特点和差异

public class Demo1 {
    public static void main(String[] args) throws IOException {
//        File f = new File("D:/test.txt");//绝对路径
        File f = new File("./test.txt");//相对路径
        System.out.println(f.getParent());
        System.out.println(f.getName());
        System.out.println(f.getPath());
        System.out.println(f.getAbsolutePath());
        System.out.println(f.getCanonicalPath());
        //绝对路径运行结果
        //D:\
        //test.txt
        //D:\test.txt
        //D:\test.txt
        //D:\test.txt
        //后面三种结果看起来一样,其实不同,如果变换构造File的路径,运行结果不同

        //相对路径运行结果
        //.
        //test.txt
        //.\test.txt
        //E:\2022code\Java-language\system_code\.\test.txt
        //E:\2022code\Java-language\system_code\test.txt
    }
}
  • 上述结果虽然运行出来了,但我们不知道是否有这个文件,可以判断文件是否存在:
import java.io.File;
import java.io.IOException;

public class Demo2 {
    public static void main(String[] args) throws IOException {
        File f = new File("./test.txt");
        //判断文件是否存在,存在就返回true,不存在就返回false
        System.out.println(f.exists()); //false
        //判断类型,是否是个目录
        System.out.println(f.isDirectory()); //false
        //判断是否是个普通文件
        System.out.println(f.isFile());  //false

        //创建这个不存在的文件,创建文件会抛出异常
        f.createNewFile();

        //判断文件是否存在,存在就返回true,不存在就返回false
        System.out.println(f.exists()); //true
        //判断类型,是否是个目录
        System.out.println(f.isDirectory()); //false
        //判断是否是个普通文件
        System.out.println(f.isFile());  //true
    }
}
  • 删除文件:
//删除文件
//创建文件成功后在项目目录下会生成一个test.txt文件,删除文件运行后,相应的文件被删除掉。

import java.io.File;

public class Demo3 {
    public static void main(String[] args) {
        File f = new File("./test.txt");
        f.delete();
    }
}
  • 除此之外还有createNewFile()创建空文件、delete()删除文件、deleteOnExit() 进程退出时删除文件、 mkdir()创建目录、mkdirs()创建多级目录、renameTo重命名等。
  • 创建目录:
//创建目录
//和删除文件一样,程序不会有任何的提示,创建目录项目会在system_code目录下生成一个目录

import java.io.File;

public class Demo4 {
    public static void main(String[] args) {
//        File f = new File("./testDir");
        File f = new File("./testDir/aaa/bbb");//创建多级目录
        //make directory 创建目录
        f.mkdir();//创建单层目录
        f.mkdirs();//创建多层目录
    }
}
  • 重命名:
//重命名

import java.io.File;

public class Demo5 {
    public static void main(String[] args) {
        File sreFile = new File("aaa.txt");
        File destFile = new File("bbb.txt");
        sreFile.renameTo(destFile);//把文件名从aaa改成bbb
    }
}
  • list():
//list() 返回 File 对象代表的目录下的所有文件名

import java.io.File;
import java.util.Arrays;

public class Demo6 {
    public static void main(String[] args) {
        File f = new File("./testDir");
        String[] results = f.list();
        System.out.println(Arrays.toString(results));
        //[aaa, bbb, ccc, ddd]
    }
}
  • 以上操作都只是在操作文件系统,并没有对文件内容进行操作。

2.读文件和写文件

  • 流(stream):我们把读写文件操作比喻成水流,将数据视为池中的水,写叫做输出流,读就是输入流(以CPU/内存为参照)。
  • 在Java中提供了一组类完成读写文件操作,按照不同的特点进行划分,可以分为字节流(以字节为基本单位)和字符流(以字符为基本单位),字节流适用于二进制文件、字符流适用于文本文件;字节流分为InputStream和OutputStream,字符流又分为Reader和Writer;上述四个类都属于父类,Java中还给这些父类提供了相应的子类来实现不同场景下的读写操作。
  • InputStream提供的方法:read()一次读一个字节,读出的结果作为read的结果;read(byte[] b)及read(byte[] b,int off, int len)都是把读到的内容放到参数字节数组b中,参数b用来存放方法读取的结果,此时b成为“输出型参数”。
  • 例如读文件:
//读文件

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class Demo7 {
    public static void main(String[] args) throws IOException {
        //InputStream 是一个抽象类,不具体,不能实例化
        //相当于打开文件操作,要想读文件就必须先打开文件
        InputStream inputStream = new FileInputStream("./bbb.txt"); 
        while (true) {
            int b = inputStream.read();
            // 读到文件末尾
            if (b == -1) {
                break;
            }
            System.out.println(b);
        }
        //读完必须关闭文件
        inputStream.close();
    }
}

// 此处使用字节流读取文件的结果是字符的ASCAII值
//字符流读文件
public class Demo2 {
    public static void main(String[] args) throws IOException {
        //使用字符流读文件
        Reader reader = new FileReader("./bbb.txt");
        while (true) {
            int ret = reader.read();
            if(ret == -1) {
                break;
            }
            char ch = (char)ret;
            System.out.println(ch);
        }
        //用完之后要关掉
        reader.close();
    }
}

// 使用字符流读文件获取到的就是文件本身的字符
// 针对文本文件,使用字符流的时候还可以使用Scanner读取文件(简化方法)
public class Demo4 {
    public static void main(String[] args) throws IOException {
        //使用Scanner 读文本文件
        InputStream inputStream = new FileInputStream("./bbb.txt");
        Scanner scanner = new Scanner(inputStream);
        while (scanner.hasNext()) {
            System.out.println(scanner.next());
        }
        inputStream.close();
    }
}

  • 写文件:

//字节流写文件
public class Demo1 {
    //进行写文件
    public static void main(String[] args) throws IOException {
        OutputStream outputStream = new FileOutputStream("./bbb.txt");

        //使用outputstream写文件的时候,只要打开文件成功,就会把原有的文件内容清空。bbb文件内容变为abc
        //小写字母的ascii码值对应的数字
        outputStream.write(97);
        outputStream.write(98);
        outputStream.write(99);

        outputStream.close();
    }
}
public class Demo3 {
    public static void main(String[] args) throws IOException {
        //使用字符流来写文件
        Writer writer = new FileWriter("./bbb.txt");
        writer.write("Hello world");
        writer.close();
    }
}
// 针对写文本文件,还可以使用 PrintWriter(简化方法)
public class Demo5 {
    public static void main(String[] args) throws IOException {
        // try with resources 把要关闭的对象写到try()里,当try结束,就会自动的调用到对应对象的close方法。
        // 而且支持一个 () 放多个对象,多个对象的创建之间使用;分割。
        try (OutputStream outputStream = new FileOutputStream("./bbb.txt")){
            PrintWriter writer = new PrintWriter(outputStream);
            
            writer.println();
            writer.print("a = %d\n, 10");
        }
    }
}

❗❗进行文件操作一定要关闭文件,原因是每个进程都对应着PCB,PCB里有一个字段是文件描述表,同一个进程里多个PCB共用一份文件描述符表,文件描述表就相当于一个数组(最大长度有上限),进程每打开一个文件就会在这个表里创建一个项(数组里的元素),关闭文件就会将这个项释放掉,如果不关闭文件,这个表项就会一直占着位置,持续打开文件且不关闭,就会耗尽表项,后续再打开文件就会打开失败(文件资源泄露,严重性大于程序崩溃)。JVM其实有自动释放策略的,但是需要被GC销毁,自动关闭对应的文件,但是这也是需要你给JVM通知一下的,可能存在隐患。服务器长期持续执行,更担心这种问题的存在。

3.练习1--扫描指定目录

//扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件
public class Demo6 {
    public static void main(String[] args) throws IOException {
        // 1.让用户输入必要的信息(要扫描的路径+要查找的词)
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要扫描的路径:");
        //判定路径是否存在
        File rootDir = new File(scanner.next());
        if (!rootDir.isDirectory()) {
            System.out.println("您输入的目录不存在!");
            return;
        }
        System.out.println("请输入要搜索的关键词:");
        String toDelete = scanner.next();

        // 2.遍历目录,找到名字配匹配的文件。
        // 需要借助一个核心方法,listFiles()
        // 能够把当前目录里的文件和子目录列举出来,但是这个方法只能列出一层,没法直接列出子目录中的子目录。
        // 解决方法就是遍历listFiles 的结果,针对每个元素,进行判定,看它是一个普通文件还是一个目录,
        // 如果是普通文件,直接判定文件名是否包含了要查的词,如果是目录,递归调用listFiles。
        scaDir(rootDir,toDelete);
    }

    //借助这个方法进行递归遍历,相当于递归遍历n叉树
    private static void scaDir(File rootDir,String toDelete) throws IOException {
        System.out.println("当前访问: " + rootDir.getCanonicalPath());
        File[] files = rootDir.listFiles();
        if (files == null) {
            //说明 rootDir 是一个空的目录
            return;
        }
        //如果目录非空,则循环遍历里面的每个元素
        for (File f : files) {
            if (f.isDirectory()) {
                scaDir(f,toDelete);
            } else {
                //不是根目录,普通文件,判定文件名是否符合要求,是否要进行删除
                checkDelete(f,toDelete);
            }
        }
    }

    // 3.询问用户是否删除
    private static void checkDelete(File f, String toDelete) throws IOException {
        if (f.getName().contains(toDelete)) {
            System.out.println("该单词" + toDelete + "被" + f.getCanonicalPath() + "包含了,是否要删除?(Y/N)");
            Scanner scanner = new Scanner(System.in);
            String choice = scanner.next();
            if (choice.equals("Y") || choice.equals("y")) {
                f.delete();
            }
        }
    }
}

升级版(性能较低,查找过程会很漫长):

//在扫描指定目录的基础上增加了查找内容的功能

public class Demo8 {
    public static void main(String[] args) throws IOException {
        // 1. 输入路径和要查询的关键词
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要扫描的路径: ");
        File rootDir = new File(scanner.next());
        System.out.println("请输入要查询的词: ");
        String toFind = scanner.next();

        // 2. 递归的扫描目录.
        scanDir(rootDir, toFind);
    }

    // 递归操作
    private static void scanDir(File rootDir, String toFind) throws IOException {
        File[] files = rootDir.listFiles();
        // 空目录
        if (files == null) {
            return;
        }
        for (File f : files) {
            if (f.isDirectory()) {
                scanDir(f, toFind);
            } else {
                checkFile(f, toFind);
            }
        }
    }

    private static void checkFile(File f, String toFind) throws IOException {
        // 1. 先检查文件名
        if (f.getName().contains(toFind)) {
            System.out.println(f.getCanonicalPath() + " 文件名中包含 " + toFind);
        }
        // 2. 再检查文件内容
        try (InputStream inputStream = new FileInputStream(f)) {
            StringBuilder stringBuilder = new StringBuilder();
            Scanner scanner = new Scanner(inputStream);
            // 按行读取
            while (scanner.hasNextLine()) {
                // 把换行拼接到文件中
                stringBuilder.append(scanner.nextLine() + "\n");
            }
            // 判断文件中是否包含要查找的词
            if (stringBuilder.indexOf(toFind) > -1) {
                System.out.println(f.getCanonicalPath() + " 文件内容包含 " + toFind);
            }
        }
    }
}

4.练习2--普通文件的复制

// 进行普通文件的复制
// 把第一个文件打开,把里面的内容逐个字节的读取出来,写到第二个文件即可
// 使用字节流来进行操作,字节流也是可以用来拷贝文本文件的

public class Demo7 {
    public static void main(String[] args) {
        // 1. 先输入需要复制的文件(源文件), 以及需要把这个文件复制到哪里去(目标文件)
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入源文件: ");
        // srcFile : d:/cat.jpg
        File srcFile = new File(scanner.next());
        System.out.println("请输入目标文件: ");
        // destFile : d:/cat2.jpg
        File destFile = new File(scanner.next());
        // 判断源文件是否存在
        if (!srcFile.isFile()) {
            System.out.println("输入源文件错误!");
            return;
        }
        // 判断目标文件是否存在
        if (!destFile.getParentFile().isDirectory()) {
            System.out.println("输入目标文件错误!");
            return;
        }
        // 2. 打开源文件, 按照字节读取内容, 依次写入到目标文件中.
        // 同时打开多个文件
        try (InputStream inputStream = new FileInputStream(srcFile);
             OutputStream outputStream = new FileOutputStream(destFile)) {
            // 读 src 的每个字节, 写入到 dest 中.
            while (true) {
                int ret = inputStream.read();
                if (ret == -1) {
                    break;
                }
                // 一边读一边写
                outputStream.write(ret);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值