【Java文件操作】文件操作常用API

Java中的文件操作可以分为两类,一类是对于 文件系统 的操作,例如获取文件路径信息、创建文件、删除文件等等;另一类是对于 文件内容 的操作,比如对于文件的读写操作。

1. 对文件系统的操作

1.1 操作方法

Java主要通过java.io.File类对文件系统进行操作。

java.io包主要用于I/O操作,I就是Input代表输入,O就是Output代表输出,这里需要区分输入和输出的流向是以CPU的视角进行看待的,从硬盘流向CPU就是输入,从CPU流出就是输出。

我们先来看看File类的常见属性、构造方法、方法。
属性:

修饰符及类型 属性 说明
static String pathSeparator 依赖于系统的路径分隔符,String类型标识
static char pathSeparator 依赖于系统的路径分隔符,char类型标识

构造方法:

签名 说明
File(String pathname) 根据文件路径创建一个File实例,路径可以是绝对路径,也可以是相对路径
File(String parent, String child) 根据父级路径+孩子文件路径,创建一个File实例
File(File parent, String child) 根据父目录+孩子文件路径,创建一个File实例

方法:

修饰符和返回类型 方法签名 说明
String getParent() 获取File对象的父目录文件路径
String getName() 获取File对象的文件名称
String getPath() 获取File对象的文件路径
String getAbsolutePath() 获取File对象的绝对文件路径
String getCanonicalPath() 获取File对象的修饰文件路径
boolean exists() 判断File对象对应文件是否真实存在
boolean isFile() 判断File对象代表文件是否是普通文件
boolean isDirectory() 判断File对象代表文件是否是目录
boolean createNewFile() 根据File对象,自动创建一个空文件,成功返回true
boolean delete() 根据File对象,删除该文件,删除成功返回true
void deleteOnExit() 根据File对象,删除该文件,在程序运行结束后删除
boolean mkdir() 创建File对象代表的目录
boolean mkdirs() 创建File对象代表的目录,若存在中间目录,一起创建
String[] list() 返回File对象代表的目录下的所有文件名
File[] listFiles() 返回File对象代表的目录下的所有文件,以File类型表示
boolean canRead() 判断用户是否对文件具有读权限
boolean canWrite() 判断用户是否对文件具有写权限
boolean renameTo(File dest) 进行文件改名,也可以视为剪切、粘贴操作

1.2 代码示例

示例1:
观察各个get方法的特点与差异

/**
 * 测试一系列get方法(绝对路径)
 */
public class TestGetMethod {
    public static void main(String[] args) throws IOException {
        File file = new File("D:/test1/test.txt");
        // 1. getParent方法
        System.out.println(file.getParent());
        // 2. getName方法
        System.out.println(file.getName());
        // 3. getPath方法
        System.out.println(file.getPath());
        // 4. getAbsolutePath方法
        System.out.println(file.getAbsolutePath());
        // 5. getCanonicalPath方法
        System.out.println(file.getCanonicalPath());
    }
}

运行结果:
【Java文件操作】文件操作常用API_第1张图片
可以发现,如果使用 绝对路径 作为File构造方法的参数,那么无论是getPath、getAbsolutePath、getCanonicalPath结果均一致,但是如果使用相对路径那么结果就不同了!

public class TestGetMethod02 {
    public static void main(String[] args) throws IOException {
        File file = new File("./test.txt");
        // 1. getParent方法
        System.out.println(file.getParent());
        // 2. getName方法
        System.out.println(file.getName());
        // 3. getPath方法
        System.out.println(file.getPath());
        // 4. getAbsolutePath方法
        System.out.println(file.getAbsolutePath());
        // 5. getCanonicalPath方法
        System.out.println(file.getCanonicalPath());
    }
}

运行结果:
【Java文件操作】文件操作常用API_第2张图片
示例2:
观察文件是否存在与判断类型方法

public class TestFileTypeAndExistsMethod {
    public static void main(String[] args) {
        File file = new File("./test.txt");
        // 1. exists方法
        System.out.println(file.exists());
        // 2. isFile方法
        System.out.println(file.isFile());
        // 3. isDirectory方法
        System.out.println(file.isDirectory());
    }
}

当我们在IDEA中创建的工作目录下新建一个test.txt文本文件,然后执行上述方法,我们可以得知该文件真实存在并且是普通文件而不是目录类型。
运行结果:
【Java文件操作】文件操作常用API_第3张图片
示例3:
观察普通文件的创建、删除方法

/**
 * 测试普通文件的新建与删除
 */
public class FileCreateAndDelete {
    public static void main(String[] args) throws IOException {
        // 1. 创建文件实例
        File file = new File("./test.txt"); // 这里要求对应物理文件不存在
        // 2. 判断对应物理文件是否存在
        boolean isExists = file.exists();
        System.out.println(isExists);
        // 3. 如果不存在则新建文件
        if (!isExists) {
            boolean createRes = file.createNewFile(); // 新建文件
            System.out.println(createRes); // 打印创建结果
        }
        // 4. 删除对应文件
        boolean deleteRes = file.delete();
        System.out.println(deleteRes); // 打印删除结果
    }
}

运行结果:
【Java文件操作】文件操作常用API_第4张图片
调用exists方法返回false,说明相对路径表示的./test.txt在物理空间上并不存在,调用createNewFile方法可以创建该文件,返回true说明创建成功!然后调用delete方法删除了该文件,删除成功则返回true

补充:如果我们使用 deleteOnExit() 方法进行删除,表示在进程结束后才会将文件进行删除,常用于临时文件,例如在日常生活中打开Word文档时会有一个$开头的临时文件出现,该文件用于对Word文档实时保存!当关闭Word文档后该文档也会随之关闭!

示例4:
观察目录的创建方法

/**
 * 目录文件的创建
 */
public class DirectoryCreate {
    public static void main(String[] args) {
        // 1. 创建目录File实例
        File file = new File("./aa"); // 这里要求物理目录不存在
        // 2. 判断是否存在
        boolean isExists = file.exists();
        System.out.println(isExists);
        // 3. 调用mkdir方法创建目录
        if (!isExists) {
            boolean createRes = file.mkdir();
            System.out.println(createRes);
        }
    }
}

运行结果:
【Java文件操作】文件操作常用API_第5张图片
当创建该File实例时,调用exists方法返回false,说明对应物理目录不存在,使用mkdir方法创建对应目录,返回true,说明创建成功!

注意:这里mkdir方法只能创建一级目录,如果创建的目录中间包含多级中间目录且中间目录均不存在时,必须使用mkdirs方法把中间目录一起创建!

示例5:
观察listFiles和list方法
我们首先在当前项目的工作目录下创建文件夹test,然后在test文件夹上创建子文件夹test1与文件file1.txt,文件结构如下:
【Java文件操作】文件操作常用API_第6张图片

/**
 * 测试listFiles与list方法
 */
public class ListFilesAndListMethod {
    public static void main(String[] args) {
        // 1. 创建目录对应File实例
        File file = new File("./test");
        // 2. 判断file是否为目录
        boolean isDirectoryRes = file.isDirectory();
        System.out.println(isDirectoryRes);
        // 3. 调用list方法
        String[] childFilesNames = file.list();
        System.out.println(Arrays.toString(childFilesNames));
        System.out.println("===============================");
        // 4. 调用listFiles方法
        File[] childFiles = file.listFiles();
        System.out.println(Arrays.toString(childFiles));
    }
}

运行结果:
【Java文件操作】文件操作常用API_第7张图片
可以发现list方法列出当前目录下的所有文件名,返回字符串数组,listFiles方法用户列出当前目录下面所有文件,返回File类型数组

2. 对文件内容的操作

**文件流:**OS提供了流的概念,Java标准库对流进行了封装后形成了一系列类用于文件的读写操作。Java的文件流可以分为两类:1、字节流;2、字符流
**字节流:**以字节作为传输数据的最小单位,代表类有InputStream、OutputStream
**字符流:**以字符作为传输数据的最小单位,一个字符可以包含多个字节,例如在UTF-8编码中1个中文字符占3个字节,代表类有Reader、Writer
【Java文件操作】文件操作常用API_第8张图片

2.1 字节输入流与输出流

Java提供了类InputStream与OutputStream用于以字节作为最小数据传输单元的流,InputStream与OutputStream都是抽象类,无法实例化对象,因此需要实例化其子类FileInputStream、FileOutputStream对文件进行读写操作。
FileInputStream读取数据:

返回类型 签名 说明
int read() 读取一个字节内容并返回,如果读取结束返回-1
int read(byte[] b) 最多读取len字节内容并写入b中,返回实际读取个数,读取结束返回-1
int read(byte[] b, int off, int len) 最多读取len字节内容到b中,从off偏移位置开始,返回实际读取的个数,读取结束返回-1

代码示例:
在编写代码之前,我们在当前工作目录下创建路径为./test/test.txt的文件,并写入内容abcdef用于测试读取。

/**
 * 使用FileInputStream读取数据
 */
public class FileInputStreamExp {
    public static void main(String[] args) {
        // 1. 创建FileInputStream
        try(InputStream inputStream = new FileInputStream("./test/test.txt")) {
            // 2. 使用read方法循环读取一个字节
            int val = 0;
            while ((val = inputStream.read()) != -1) {
                System.out.printf("%x ", val); // 十六进制表示
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

运行结果:
【Java文件操作】文件操作常用API_第9张图片
上述结果正是abcdef对应ASCII码值的十六进制表示形式。
但是循环读取单个字节在数据量庞大的时候效率低下,我们使用 输出型参数 的的byte[]数组可以一次性读入多个字节存放到字节数组中,下面是代码示例:

/**
 * 使用输出型参数byte[] 一次性读取多个字节
 */
public class FileInputStream02 {
    public static void main(String[] args) {
        // 1. 创建FileInputStream实例
        try(InputStream inputStream = new FileInputStream("./test/test.txt")) {
            // 2. 使用字节数组读取
            byte[] buffer = new byte[1024];
            int n = inputStream.read(buffer);
            // 将前n个字节转化为String并打印
            String str = new String(buffer, 0, n);
            System.out.println(str);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

运行结果:
【Java文件操作】文件操作常用API_第10张图片
我们从文件中一次性读取多个字节写入字节数组buffer中,然后获取前n个字节内容(n为实际读取的字节个数)转化为String类型并打印,得到结果abcdef
FileOutputStream写入数据:

返回类型 签名 说明
void write(int b) 向文件中写入一个字节内容
void write(byte[] b) 将字节数组中b.length长度内容写入文件
void write(byte[] b, int off, int len) 将字节数组中从偏移量off开始len长度内容写入文件

代码示例:
在编写代码之前,我们在当前工作目录下创建路径为./test/testWrite.txt的空文件,用来测试写文件操作

/**
 * 测试写入一个字节
 */
public class TestWrite {
    public static void main(String[] args) {
        // 1. 打开文件
        try(FileOutputStream fos = new FileOutputStream("./test/testWrite.txt")) {
            // 2. 写入一个字节内容
            byte b = 97;
            fos.write(b);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

执行上述代码后我们会发现./test/testWrite.txt文件中出现了a,证明写入成功!下面我们测试以字节数组作为参数的write方法。

/**
 * 测试写入整个字节数组
 */
public class TestWriteByteArr {
    public static void main(String[] args) {
        // 1. 打开文件
        try(FileOutputStream fos = new FileOutputStream("./test/testWrite.txt")) {
            // 2. 将字符串"hello world"变为字节数组
            byte[] content = "hello world".getBytes();
            // 3. 写入文件
            fos.write(content);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

此时我们发现./test/testWrite.txt中的内容已经变为了"hello world",可以证明写入字节数组内容成功!但是我们还发现一个问题,原来的a怎么不见了,难道Java写文件的时候是覆盖写的么?

补充:Java中若使用文件输出流,那么在打开文件的时候,就会清空其中的内容,那么我们如何做到向文件追加写入呢?只需要在打开文件的时候传递参数true即可,例如FileOutputStream fos = new FileOutputStream(“./test/testWrite.txt”, true)就可以实现追加写了。

2.2 字符输入流与输出流

同样的,Java提供Reader与Writer类用于字符流数据的传输,它们同样都是抽象类,我们需要实例化其子类FileReader、FileWriter对象用于对文件进行读写操作。
FileReader读取数据:

返回类型 签名 说明
int read() 从文件中读取一个字符内容,读取完毕返回-1
int read(char[] cbuf) 从文件中读取若干字符到字符数组中,返回实际读取长度,读取完毕返回-1
int read(char[] cbuf, int off, int len) 从文件中读取若干字符到字符数组中,从偏移位置off开始,返回实际读取长度,读取完毕返回-1

代码示例:
在编写代码之前,我们在当前工作目录下创建路径为./test/testFileReaderRead.txt的文件,并写入内容abcdef用来测试读取方法

/**
 * 使用FileReader读取一个字符
 */
public class FileReader01 {
    public static void main(String[] args) {
        // 1. 打开文件
        try(FileReader fr = new FileReader("./test/testFileReaderRead.txt")) {
            int ret = 0;
            while ((ret = fr.read()) != -1) {
                // 2. 循环读取1个字符并打印
                System.out.printf("%c\n", ret);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

运行结果:
【Java文件操作】文件操作常用API_第11张图片
我们循环读取文件中的内容,并返回读取的单个字符,我们将其进行打印,直到-1结束循环,运行结果符合预期!接下来我们尝试一次性读取多个字符

/**
 * 一次性读取多个字符
 */
public class FileReader02 {
    public static void main(String[] args) {
        // 1. 打开文件
        try(FileReader fr = new FileReader("./test/testFileReaderRead.txt")) {
            // 2. 循环读取多个字符内容
            int len = 0;
            char[] cbuff = new char[1024];
            while ((len = fr.read(cbuff)) != -1) {
                // 打印
                System.out.println(new String(cbuff, 0, len));
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

运行结果:
【Java文件操作】文件操作常用API_第12张图片
FileWriter写入数据:

返回类型 签名 说明
void write(int c) 写入一个字符数据
void write(char[] cbuf) 写入一个字符数组数据
void write(char[] cbuf, int off, int len) 写入字符数组部分内容,从偏移量off开始,写入长度为len
void write(String str) 写入字符串数据
void write(String str, int off, int len) 写入字符串部分内容,从偏移量off开始,写入长度为len

代码示例:
在编写代码之前,我们在当前工作目录下创建路径为./test/testFileWriterWrite.txt的空文件,并使用上述write方法尝试向该文件中写入内容,虽然重载的方法有很多,但是最常用的还是write(String str)方法

/**
 * 测试使用FileWriter写入字符串
 */
public class TestFileWriter {
    public static void main(String[] args) {
        // 1. 打开文件
        try(FileWriter fw = new FileWriter("./test/testFileWriterWrite.txt")) {
            // 2. 写入字符串
            fw.write("这是一段中文。。。");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

3. 文件操作案例

我们已经学会了如何使用基本的文件操作API,那么接下来让我们写几个小案例来熟练一下叭!

3.1 普通文件的复制

要求:由用户输入原普通文件的路径(原文件必须存在),然后让用户输入需要复制的目标文件路径(目标文件可以不存在)

** 参考代码:**

/**
 * 由用户输入原普通文件的路径(原文件必须存在),然后让用户输入需要复制的目标文件路径(目标文件可以不存在)
 */
public class FileCopy {
    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        // 1. 用户输入原文件路径
        System.out.println("请输入原文件路径:");
        String srcPath = scanner.next();
        // 2,判断原文件合法性
        File srcFile = new File(srcPath);
        if (!srcFile.exists()) {
            System.out.println("原文件不存在!");
            return;
        }
        if (!srcFile.isFile()) {
            System.out.println("原文件不是普通文件!");
            return;
        }
        // 3. 用户输入目标文件路径
        System.out.println("请输入目标文件路径:");
        String dstPath = scanner.next();
        File dstFile = new File(dstPath);
        // 4. 合法性判断
        if (!dstFile.getParentFile().exists()) {
            System.out.println("目标路径不合法!");
        }
        if (!dstFile.getParentFile().isDirectory()) {
            System.out.println("目标路径不合法!");
        }
        // 5. 目标路径不存在就创建普通文件
        if (!dstFile.exists()) {
            boolean createRes = dstFile.createNewFile();
            if (!createRes) {
                System.out.println("文件创建失败!");
                return;
            }
        }
        // 6. 文件复制
        fileCopy(srcFile, dstFile);
    }

    /**
     * 文件拷贝方法
     * @param srcFile
     * @param dstFile
     * @return
     */
    private static void fileCopy(File srcFile, File dstFile) {
        try(FileInputStream fis = new FileInputStream(srcFile);
            FileOutputStream fos = new FileOutputStream(dstFile)) {
            int len = 0;
            byte[] buffer = new byte[1024];
            while ((len = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, len);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

3.2 递归扫描

要求:扫描指定目录,并找到名称或者内容中包含指定字符的所有普通文件(不包含目录)
注意:我们现在的方案性能较差,所以尽量不要在太复杂的目录下或者大文件下实验
参考代码:

/**
 * 要求:扫描指定目录,并找到名称或者内容中包含指定字符的所有普通文件(不包含目录)
 */
public class FileScan {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        // 1. 用户输入扫描目录
        System.out.println("请输入想要扫描的目录路径:");
        String dirPath = scanner.next();
        File dirFile = new File(dirPath);
        // 2. 判断合法性
        if (!dirFile.exists()) {
            System.out.println("该目录不存在!");
            return;
        }
        if (!dirFile.isDirectory()) {
            System.out.println("该目录不合法!");
            return;
        }
        // 3. 用户输入查找内容
        System.out.println("请输入查找内容:");
        String searchContent = scanner.next();
        // 4. 递归查找
        findDFS(dirFile, searchContent);
    }

    private static void findDFS(File dir, String content) {
        // 列出当前目录所有文件
        File[] childFiles = dir.listFiles();
        for (File childFile : childFiles) {
            if (childFile.isFile()) {
                // 如果是普通文件
                try(FileInputStream fis = new FileInputStream(childFile)) {
                    StringBuilder sBuilder = new StringBuilder();
                    byte[] buffer = new byte[1024];
                    int len = 0;
                    while ((len = fis.read(buffer)) != -1) {
                        // 循环读取
                        sBuilder.append(new String(buffer, 0, len));
                    }
                    // 比较内容
                    if (sBuilder.indexOf(content) != -1) {
                        // 说明存在
                        System.out.println("查找到相关内容,在文件" + childFile.getCanonicalPath() + "中");
                    }
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            } else {
                // 说明是目录递归查找
                findDFS(childFile, content);
            }
        }
    }
}

3.3 递归删除

要求:扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件
参考代码:

/**
 * 要求:扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件
 */
public class FileDelete {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        // 1. 用户输入扫描目录
        System.out.println("请输入需要扫描的目录路径:");
        String scanDirPath = scanner.next();
        File scanDirFile = new File(scanDirPath);
        // 2. 检验合法性
        if (!scanDirFile.exists()) {
            System.out.println("该目录不存在!");
            return;
        }
        if (!scanDirFile.isDirectory()) {
            System.out.println("该目录不合法!");
            return;
        }
        // 3. 由用户输入查找字符
        System.out.println("请输入查找字符:");
        String searchContent = scanner.next();
        // 4. 递归查找
        searchDFS(scanDirFile, searchContent);
    }

    private static void searchDFS(File dir, String content) {
        // 列出所有子级文件
        File[] childFiles = dir.listFiles();
        for (File childFile : childFiles) {
            if (childFile.isDirectory()) {
                // 是目录继续递归
                searchDFS(childFile, content);
            } else {
                // 普通文件就获取所有内容
                try(FileInputStream fis = new FileInputStream(childFile)) {
                    StringBuilder sBuilder = new StringBuilder();
                    int len = 0;
                    byte[] buffer = new byte[1024];
                    while((len = fis.read(buffer)) != -1) {
                        sBuilder.append(new String(buffer, 0, len));
                    }
                    // 查找
                    if (sBuilder.indexOf(content) != -1) {
                        System.out.println("找到了目标文件:" + childFile.getCanonicalPath());
                        // 是否确认删除
                        Scanner scanner = new Scanner(System.in);
                        while (true) {
                            System.out.println("是否删除?(Y/N)");
                            String answer = scanner.next();
                            if (answer.equalsIgnoreCase("Y")) {
                                // 进行删除
                                childFile.deleteOnExit();
                                break;
                            } else if (answer.equalsIgnoreCase("N")) {
                                // 不进行删除
                                break;
                            } else {
                                // 输入有误
                                System.out.println("输入有误,请重新输入!");
                            }
                        }
                    }
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

你可能感兴趣的:(java)