【JavaEE】(4) 文件操作和IO

发布于:2025-06-27 ⋅ 阅读:(18) ⋅ 点赞:(0)

一、文件

        计算机中,文件保存在硬盘上,但是在程序中直接操作硬盘很复杂,操作系统就将硬盘封装成 “文件”,编程时,我们只需要操作打开文件、关闭文件、读文件、写文件等接口。操作系统通过文件管理器管理文件,所有文件构成一个 N 叉树结构:

所以可以通过文件路径描述一个文件的位置。对于每一级目录,windows 系统用 /(斜杠)、\(反斜杠)来划分;Linux、MacOS、Android 等用 / 划分。有相对路径绝对路径,相对路径是相对于基准路径./ 表示当前目录,../ 表示上一级目录。

二、文件的种类

  • 文本文件:存合法字符,数据的解析方式基于字符编码。

ascii:用数字表示英文字母、标点符号、阿拉伯数字。

后面引入更大的码表,表示汉字、俄语字符等。

gbk:2 个字符表示一个汉字。(windows 简体中文默认 gbk、VS 默认编码跟随系统)

utf-8(主流):utf-8 变长编码,2~4 个字节,一般汉字 3 个字节。

java 中一个 char 用到 utf-16(Unicode,定长) 是 2 个字符表示一个汉字,String 会在内部转为 utf-8 的形式。

  • 二进制文件:存什么都可以,比如图片、视频等。

三、文件操作

  • 文件系统操作:创建文件、删除文件、移动文件、获取文件属性等。
  • 文件内容操作:读文件、写文件。

1、File 类(文件系统操作)

  • 属性

pathSeparator :依赖系统的路径分隔符。

  • 构造函数

File(String pathname):pathname 是文件路径,相对或绝对。

File(String parent, String child):与基准路径拼接,字符串形式。

File(File parent,String child):与基准路径拼接,File 类形式。

对于开发工具来说,基准路径就是终端上显示的路径:

  • 方法

getParent():获取 File 的父目录

getName():获取 File 的文件名称

getPath():获取 File 的路径,看创建对象时的路径

getAbsolutePath():获取 File 的绝对路径,基准路径与相对路径单纯的拼接

getCanonicalPath():获取 File 的绝对路径,会去掉冗余的符号

绝对路径示例:

相对路径示例:

  • exists():File 文件是否真实存在。
  • isDirectory():判断 File 是否是一个目录。
  • isFile():判断 File 是否是一个普通文件。

  • createNewFile():创建文件,创建成功返回 true。
  • delete():删除成功返回 true。
  • deleteOnExit():程序结束后,才会删除。(就像编辑 word,会对修改内容保存一个隐藏文件,当 word 内存中内容保存到磁盘后,程序退出,隐藏文件才会删除)

  • list():列出目录下的所有文件名,返回的是 String[]
  • listFiles():列出目录下的所有文件,返回的是 File[]

  • mkdir():创建一层目录
  • mkdirs():创建多层目录

  • renameTo(File dest):文件改名移动

2、流对象(文件内容操作)

        读(输入)文件和写(输出)文件都是相对于 CPU 的,读文件:将数据从硬盘读到内存;写文件:将数据从内存写到硬盘。

        流对象主要分为两大类

  • 字节流:字节为单位读(InputStream)写(OutputStream),常用于读写二进制文件
  • 字符流:字符为单位读(Reader)写(Writer)(具体几个字节看编码方式,至少一个字节),常用于读写文本文件

PS:C/C++ 没有字符流的概念,只能按字节读取。如果想读取像汉字这样的多字节字符,那么首先得识别这个字符串的二进制序列使用的什么编码方式,然后判断从哪到哪是一个完整的汉字,最后在编码表中对应起来。

2.1、InputStream(字节流)

        InputStream 是个抽象类,不能创建实例,我们通过文件输入流 FileInputStream 创建实例(不仅文件有流得概念,网络等也有)。FileInputStream 会抛出 FileNotFoundException 异常。

        三种参数的 read 方法:会抛出 IOException 异常,而 IOException 是 FileNotFoundException 的父类

  • read():每次读取一个字节。读取成功返回 0~255 的整数;读取失败返回 -1

  • read(byte[] b):最多读取 b.length 个字节到 b(输出型参数) 中,返回读取到的字节数读取完毕则返回 -1(之所以设计 b 为输出型参数,是因为 java 只能有一个返回值,而已经有了返回值 n)。

  • read(byte[] b, int off, int len)从 off 位置开始,最多读取 len 个字节。
  • close():读取完毕后 ,需要关闭文件。进程的 PCB 中有一个文件描述符表(顺序表)的属性,它不能扩容。当程序一直运行,并打开文件却不关闭文件,那么表的空间就会耗尽。耗尽后再尝试打开,就会失败。这属于“文件资源泄露”。

        但是这样写还存在一个问题,比如下面这种写法,与上面的效果一样:

        将 close 写到 finally,无论以何种形式退出,都会执行 close:

        但是这样写代码就很丑,可读性降低,所以我们使用 try-with-resource 写法,会自动 close

2.2、OutputStream(字节流)

  • write(int b):写入一个字节。
  • write(byte[] b):写入一个字节数组。
  • write(byte[] b, int off, int len):写入一个字节数组的一部分。

        按 OutputStream 每次打开文件,都会清空文件内容。为了不清空,需要加上 append 参数为 true

2.3、Reader(字符流)

read 返回的是 0x00 ~ 0xff 的一个两字节整数,需要转换为 char

2.4、Writer(字符流)

3、案例练习

3.1、案例1

       扫描指定目录,找到所有包含指定字符的普通文件,并询问用户是否删除。

import java.io.File;
import java.io.IOException;
import java.util.Scanner;

public class Demo10 {
    public static void main(String[] args) {
        // 1. 接收用户输入,指定目录文件名、字符串
        Scanner scanner = new Scanner(System.in);
        System.out.println("输入扫描的文件名:");
        String dirname = scanner.nextLine();
        System.out.println("输入删除的文件名包含的字符串:");
        String filestr = scanner.nextLine();

        // 2. 扫描目录文件
        File dir = new File(dirname);
        if(!dir.isDirectory()) {
            System.out.println("该目录不存在!");
        }
        scanDir(dir, filestr);
        scanner.close();
    }

    private static void scanDir(File dir, String filestr) {
        // 获得目录下的文件列表
        File[] files = dir.listFiles();
        // 判断文件列表是否为空
        if(files == null) {
            System.out.println("目录文件为空!");
            return;
        }
        // 遍历文件列表,删除包含字符串的普通文件
        for(File file : files) {
            String filename = file.getName();
            // 是一个普通文件,且文件名包含指定字符串,尝试删除文件
            if(file.isFile() && filename.contains(filestr)) {
                tryDeleteFile(file);
            // 是一个目录文件,递归扫描
            }else if(file.isDirectory()) {
                scanDir(file, filestr);
            }
        }
    }

    private static void tryDeleteFile(File file) {
        Scanner scanner = new Scanner(System.in);
        try {
            System.out.println("是否删除文件 " + file.getCanonicalPath() + "(Y or N):");
        } catch (IOException e) {
            e.printStackTrace();
        }
        String choice = scanner.nextLine();
        if(choice.equalsIgnoreCase("Y")) {
            if(file.delete()) {
                System.out.println("文件删除成功!");
            } else {
                System.out.println("文件删除失败!");
            }
        }
        scanner.close();
    }
}

3.2、案例2

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Scanner;

public class Demo11 {
    public static void main(String[] args) {
        // 1. 接收用户输入,指定源文件、目标文件
        Scanner scanner = new Scanner(System.in);
        System.out.println("输入源文件路径:");
        String srcname = scanner.nextLine();
        System.out.println("输入目标文件路径:");
        String destname = scanner.nextLine();
        scanner.close();

        // 2. 判断源文件是否存在,目标文件的父目录是否存在
        File srcfile = new File(srcname);
        File destfile = new File(destname);
        if(!srcfile.isFile()) {
            System.out.println("源文件不存在!");
            return;
        }
        System.out.println(destfile.getParentFile());
        if(!destfile.getParentFile().isDirectory()) {
            System.out.println("目标文件的父目录不存在!");
            return;
        }

        // 3. 读取源文件内容,并写入目标文件
        copy(srcfile, destfile);
    }

    private static void copy(File srcfile, File destfile) {
        try (InputStream inputStream = new FileInputStream(srcfile);
             OutputStream outputStream = new FileOutputStream(destfile)) {
            byte[] bytes = new byte[1024];
            while(true) {
                int n = inputStream.read(bytes);
                if(n == -1) {
                    break;
                }
                outputStream.write(bytes, 0, n);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}