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

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

1. 对文件系统的操作

1.1 操作方法

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

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

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

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

构造方法:

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

方法:

修饰符和返回类型方法签名说明
StringgetParent()获取File对象的父目录文件路径
StringgetName()获取File对象的文件名称
StringgetPath()获取File对象的文件路径
StringgetAbsolutePath()获取File对象的绝对文件路径
StringgetCanonicalPath()获取File对象的修饰文件路径
booleanexists()判断File对象对应文件是否真实存在
booleanisFile()判断File对象代表文件是否是普通文件
booleanisDirectory()判断File对象代表文件是否是目录
booleancreateNewFile()根据File对象,自动创建一个空文件,成功返回true
booleandelete()根据File对象,删除该文件,删除成功返回true
voiddeleteOnExit()根据File对象,删除该文件,在程序运行结束后删除
booleanmkdir()创建File对象代表的目录
booleanmkdirs()创建File对象代表的目录,若存在中间目录,一起创建
String[]list()返回File对象代表的目录下的所有文件名
File[]listFiles()返回File对象代表的目录下的所有文件,以File类型表示
booleancanRead()判断用户是否对文件具有读权限
booleancanWrite()判断用户是否对文件具有写权限
booleanrenameTo(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());
    }
}

运行结果:
image.png
可以发现,如果使用 绝对路径 作为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());
    }
}

运行结果:
image.png
示例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文本文件,然后执行上述方法,我们可以得知该文件真实存在并且是普通文件而不是目录类型。
运行结果:
image.png
示例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); // 打印删除结果
    }
}

运行结果:
image.png
调用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);
        }
    }
}

运行结果:
image.png
当创建该File实例时,调用exists方法返回false,说明对应物理目录不存在,使用mkdir方法创建对应目录,返回true,说明创建成功!

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

示例5:
观察listFiles和list方法
我们首先在当前项目的工作目录下创建文件夹test,然后在test文件夹上创建子文件夹test1与文件file1.txt,文件结构如下:
image.png

/**
 * 测试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));
    }
}

运行结果:
image.png
可以发现list方法列出当前目录下的所有文件名,返回字符串数组,listFiles方法用户列出当前目录下面所有文件,返回File类型数组

2. 对文件内容的操作

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

2.1 字节输入流与输出流

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

返回类型签名说明
intread()读取一个字节内容并返回,如果读取结束返回-1
intread(byte[] b)最多读取len字节内容并写入b中,返回实际读取个数,读取结束返回-1
intread(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);
        }
    }
}

运行结果:
image.png
上述结果正是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);
        }
    }
}

运行结果:
image.png
我们从文件中一次性读取多个字节写入字节数组buffer中,然后获取前n个字节内容(n为实际读取的字节个数)转化为String类型并打印,得到结果abcdef
FileOutputStream写入数据:

返回类型签名说明
voidwrite(int b)向文件中写入一个字节内容
voidwrite(byte[] b)将字节数组中b.length长度内容写入文件
voidwrite(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读取数据:

返回类型签名说明
intread()从文件中读取一个字符内容,读取完毕返回-1
intread(char[] cbuf)从文件中读取若干字符到字符数组中,返回实际读取长度,读取完毕返回-1
intread(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);
        }
    }
}

运行结果:
image.png
我们循环读取文件中的内容,并返回读取的单个字符,我们将其进行打印,直到-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);
        }
    }
}

运行结果:
image.png
FileWriter写入数据:

返回类型签名说明
voidwrite(int c)写入一个字符数据
voidwrite(char[] cbuf)写入一个字符数组数据
voidwrite(char[] cbuf, int off, int len)写入字符数组部分内容,从偏移量off开始,写入长度为len
voidwrite(String str)写入字符串数据
voidwrite(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);
                }
            }
        }
    }
}
  • 24
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值