• 【JavaEE初阶】文件操作 和 IO (下篇)


    ☕导航小助手☕

        🍚写在前面

              🧇三、文件内容的操作

                   🍣🍣3.1 读文件

                             🧀🧀🧀3.1.1 使用字节流读文件

                             🥡🥡🥡3.1.2 使用字符流读文件 

                             🦪🦪🦪3.1.3 使用Scanner读取文件(推荐)

                   🍱🍱3.2 写文件

                             🍛🍛🍛3.2.1 使用字节流写文件

                             🍰🍰🍰3.2.2 使用字符流写文件

                             🥩🥩🥩3.2.3 使用PrintWriter写文件

              🍜四、代码案例

                   🍤🍤4.1 文件查找功能

                   🍔🍔4.2 复制普通文件

                   🥐🥐4.3 获取含有指定字符串的普通文件


    写在前面

    这篇博客是关于 文件操作和IO 的下半篇的内容,主要介绍的是 文件内容的各种操作 ~

    若想看上半篇的内容,请通过下面的传送门进行传送:

    🚪传送门🚪【JavaEE初阶】文件操作 和 IO (上篇)

    现在,接着上半篇的内容继续介绍 ......

    三、文件内容的操作

    关于文件读写的操作,主要分为这几大块:打开文件、关闭文件、读文件、写文件 ~

    其中,读写文件 是关键操作 ~

    但是,在读写文件之前,必须要打开文件;在使用完毕之后,必须要关闭文件 ~

    在 Java中,关于文件读写,提供了几组相关的类(是父类,还有许多其他子类继承):

    1. 第一组:InputStream类、OutputStream类 ——> 字节流(以字节为单位的流,用来操作二进制文件的)
    2. 第二组:Reader类、Writer类 ——> 字符流(以字符为单位的流,用来操作文本文件的)

    关于 "流",是计算机中一种常见的概念 ~

    比如说,如果想接住 100ml的水 ~

    可以一次接 100ml,一次就搞定;可以一次接 50ml,两次就搞定;可以一次接 10ml,十次就搞定 ......

    类似于水流,可以随心所欲的控制 读写的数据量 ~

    如:想 读/写 100字节的数据 ~

    可以一次 读/写100字节,一次就搞定;可以一次 读/写50字节,两次就搞定;可以一次 读/写10字节,十次就搞定 ......

    于是,我们就把这种 读/写 方式,称为 面向流的读/写方式 ~ 


    3.1 读文件

    3.1.1 使用字节流读文件

    我们可以在 Idea 上输入 InputStream类,点击 Ctrl + 左键,便可以发现,InputStream 是一个抽象类,不可以直接 new,需要使用 new InputStream 子类的方式来进行实例化: 

    方法:

    关于 InputStream类 的常用方法有下面几个:

    修饰符及返回值类型方法说明
    int(其实返回的是 byte)read()读取一个字节的数据,如果读取到文件末尾(EOF),则返回 -1
    intread(byte[ ]  b)

    把文件中读到的内容,往 b数组中塞,b数组是"输出型参数";返回值是 读取成功的字节个数(往 b数组 塞的个数);-1表示已经读完了

    intread(byte[ ] b,int off,int len)从 b[ off ] 这个位置开始塞,最多塞 len - off 这么多个字节  ;返回值是 读取成功的字节个数(往 b数组 塞的个数);-1表示已经读完了
    voidclose()关闭字节流

    需要注意的是,要在打开之后 一定记得要关闭文件!!!

    —— 需要释放资源(内存、文件描述符表)

    —— 如果打开文件之后,忘记关闭,造成的结果非常大的(文件资源泄露,就像是定时炸弹一样,并不是马上就泄露完,这就非常的麻烦了)~

    一个进程,会使用PCB来描述(此时 只考虑单线程)~

    PCB 里面有很多属性,文件描述符表 就是其中的一个属性,它可以看作是一个数组(顺序表),其中的每个元素 都对应着当前打开的文件,这个数组的下标 就称为 "文件描述符" ~

    每次打开文件,都会在文件描述符表中 占据一个位置,每次关闭文件,都会释放一个位置;由于文件描述符表的长度是存在上限的,所以如果一个线进程一直在打开文件,没有释放文件,此时就会导致 后续进程在打开的时候失败!!!

    代码示例:

    1. package file;
    2. import java.io.FileInputStream;
    3. import java.io.FileNotFoundException;
    4. import java.io.IOException;
    5. import java.io.InputStream;
    6. public class Demo6 {
    7. //使用一下 InputStream类
    8. public static void main(String[] args) throws IOException {
    9. // 1.打开文件
    10. //打开文件成功,就得到了 InputStream对象,
    11. //后续针对文件的操作,都是通过 InputStream 来展开的
    12. // (就相当于是 空调遥控器 可以操控空调,内存中的 InputStream对象 可以操控硬盘里面的 test2.txt文件)
    13. //像 inputStream 这一类的 “遥控器”,在计算机中 我们通常称为:句柄(Handler)
    14. InputStream inputStream = new FileInputStream("./test2.txt");
    15. // 2.读取文件
    16. while (true) {
    17. int b = inputStream.read();
    18. if(b == -1) {
    19. //此时,文件读完了
    20. break;
    21. }
    22. System.out.println(b);
    23. }
    24. // 3.关闭文件
    25. //关闭文件,以释放 内存资源 和 文件描述符表
    26. inputStream.close();
    27. }
    28. }

    同时,我在 test2.txt 文件下输入了:hello 

    运行结果:


    1. -- 当然,我们也可以采用其他的 read方法 来读取数据
    2. -- 这里可以把 2.读取文件 的代码改成:
    3. byte[] b = new byte[1024];
    4. int len = inputStream.read(b);
    5. System.out.println(len);
    6. for (int i = 0; i < len; i++) {
    7. System.out.println(b[i]);
    8. }

     此时,运行的结果是:

     

    3.1.2 使用字符流读文件 

    需要注意的是,在上面的 test2.txt 文件中 存储的是英文状态的 "hello",但是但我们把它改成中文状态的 "你好" 时,那结果可就不一样了 ~

    运行结果:

    说明:

    当前格式 是 utf-8 的格式编码,它的汉字一般是 三个字节一个汉字 ~

    如果想要直观的获取 中文,那就需要去手动的转换(如下所示):

    1. -- 这里可以把 2.读取文件 的代码改成:
    2. byte[] b = new byte[1024];
    3. int len = inputStream.read(b);
    4. //0到len 这一段数据构成一个字符串,并且指定构造字符集的格式编码 —— utf-8
    5. String s = new String(b,0,len,"utf-8");
    6. System.out.println(s);

    运行结果:


    通过字节流,读取文本数据的时候,虽然可以读取到,但是要想真正还原成原始的版本,还是比较麻烦的,需要手动处理~

    因此就可以使用 字符流 来解决上述问题(字符流 就相当于已经在底层 已经完成了里面的转换了)~ 


    代码示例:

    1. package file;
    2. import java.io.FileNotFoundException;
    3. import java.io.FileReader;
    4. import java.io.IOException;
    5. import java.io.Reader;
    6. public class Demo7 {
    7. //使用字符流读文件
    8. public static void main(String[] args) throws IOException {
    9. Reader reader = new FileReader("test2.txt");
    10. char[] buffer = new char[1024];
    11. int len = reader.read(buffer);
    12. for (int i = 0; i < len; i++) {
    13. System.out.print(buffer[i]);
    14. }
    15. //关闭资源
    16. reader.close();
    17. }
    18. }

    运行结果:

    3.1.3 使用Scanner读取文件(推荐)

    字符流来处理文本是比较方便的,但是 这里还有一种更为简单的方法,可以使用 Scanner 来读取文本文件 ~

    1. Scanner scanner = new Scanner(System.in);
    2. //以前我们使用的是这个样子的,可以从键盘上来输入数据
    3. //其中,System.in 中的 in 就是 inputStream
    4. //如果直接把 System.in 换成 inputStream
    5. //需要 InputStream inputStream = new FileInputStream("文本");
    6. //就可以去读自己写的文件

    代码示例:

    1. package file;
    2. import java.io.FileInputStream;
    3. import java.io.FileNotFoundException;
    4. import java.io.IOException;
    5. import java.io.InputStream;
    6. import java.util.Scanner;
    7. public class Demo8 {
    8. //使用 Scanner 来读取文本文件
    9. public static void main(String[] args) throws IOException {
    10. InputStream inputStream = new FileInputStream("test2.txt");
    11. Scanner scanner = new Scanner(inputStream);
    12. String s = scanner.next();
    13. System.out.println(s);
    14. inputStream.close();
    15. }
    16. }

    运行结果:

     

    需要注意的是,其实在上述代码中,涉及到了 read操作,这就会引出 IOException异常,此时代码就会无法进行,后面的 关闭文件(close ) 的操作就不会被运行 ~

    所以可以使用 try ... catch ... finally ... 的操作来避免的 ~

    这里就不做过多演示了 ~

    但是,最后就会发现,这个看起来就很啰嗦 ~

    所以,就可以改成这个样子了 :

    这样的操作叫做 try with resource(有资源的 try)~ 

    同时,这类写法 会在 try 实现结束之后,自动调用 inputStream 的 close方法(当然,并不是所有的类都可以这样放在try括号里面,要求这个类实现 Closeable接口,才可以这么做)~、

    这种写法是比较推荐的写法~

    3.2 写文件

    字节流:OutputStream / FileOutputStream

    字符流:Writer / FileWriter

    以及,和 Scanner 相对的 PrintWriter 操作

    OutoutStream类 的方法和上面的 InputStream类 的方法差不多,这里就不做过多介绍了,感兴趣的铁铁们可以自己去看看官方文档 ~

    3.2.1 使用字节流写文件

    1. package file;
    2. import java.io.FileOutputStream;
    3. import java.io.IOException;
    4. import java.io.OutputStream;
    5. public class Demo10 {
    6. public static void main(String[] args) {
    7. try(OutputStream outputStream = new FileOutputStream("test2.txt")) {
    8. //写文件
    9. outputStream.write('h');
    10. outputStream.write('e');
    11. outputStream.write('l');
    12. outputStream.write('l');
    13. outputStream.write('o');
    14. }catch (IOException e) {
    15. e.printStackTrace();
    16. }
    17. }
    18. }

    当我们运行这段代码时,再去查看 text2.txt 文档时,就会发现:

    如果想写一个字符串,那么就可以这样做:

    1. package file;
    2. import java.io.FileOutputStream;
    3. import java.io.IOException;
    4. import java.io.OutputStream;
    5. import java.nio.charset.StandardCharsets;
    6. public class Demo10 {
    7. public static void main(String[] args) {
    8. try(OutputStream outputStream = new FileOutputStream("test2.txt")) {
    9. //写文件
    10. String s = "hello java";
    11. outputStream.write(s.getBytes());
    12. }catch (IOException e) {
    13. e.printStackTrace();
    14. }
    15. }
    16. }

    此时,运行程序后,再去看看 test2.txt 文件,就会发现:

    需要注意的是,每一次 写文件 的操作都会清空原有的文件,然后再去重新写 ~ 

    3.2.2 使用字符流写文件

    1. package file;
    2. import java.io.FileWriter;
    3. import java.io.IOException;
    4. import java.io.Writer;
    5. public class Demo11 {
    6. public static void main(String[] args) {
    7. try(Writer writer = new FileWriter("test2.txt")) {
    8. writer.write("hello world");
    9. }catch (IOException e) {
    10. e.printStackTrace();
    11. }
    12. }
    13. }

    此时,运行程序之后,再次看看 test2.txt 文件,就会发现:

    3.2.3 使用PrintWriter写文件

    1. package file;
    2. import java.io.*;
    3. public class Demo12 {
    4. public static void main(String[] args) {
    5. try (OutputStream outputStream = new FileOutputStream("test2.txt")) {
    6. //此处的 PrintWriter 的用法 和 System.out 有相似之处
    7. PrintWriter printWriter = new PrintWriter(outputStream);
    8. printWriter.println("正在看博客的人好帅啊");
    9. printWriter.flush();
    10. }catch (IOException e) {
    11. e.printStackTrace();
    12. }
    13. }
    14. }

     此时,程序运行结束之后,再来看看 test2.txt 文件:

    四、代码案例

    4.1 文件查找功能

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

    1. package file;
    2. import java.io.File;
    3. import java.io.IOException;
    4. import java.util.Scanner;
    5. public class Demo13 {
    6. // 实现一个递归遍历文件, 并询问删除.
    7. public static void main(String[] args) {
    8. Scanner scanner = new Scanner(System.in);
    9. System.out.println("请输入要扫描的路径: ");
    10. String rootPath = scanner.next();
    11. //输入的路径不存在
    12. File root = new File(rootPath);
    13. if (!root.exists()) {
    14. System.out.println("您输入的路径不存在, 无法进行扫描!");
    15. return;
    16. }
    17. System.out.println("请输入要删除的文件名(或者一部分): ");
    18. String toDelete = scanner.next();
    19. // 准备进行递归, 通过递归的方式, 来找到所有的文件.
    20. // 找到所有的文件之后, 再尝试进行删除
    21. scanDir(root, toDelete);
    22. }
    23. //rootDir 从哪个路径开始扫描 toDelete 要删除的文件
    24. public static void scanDir(File rootDir, String toDelete) {
    25. // 加上个日志, 看一下这里当前递归的过程.
    26. try {
    27. System.out.println(rootDir.getCanonicalPath());
    28. } catch (IOException e) {
    29. e.printStackTrace();
    30. }
    31. //列出扫描目录有哪些文件
    32. File[] files = rootDir.listFiles();
    33. if (files == null) {
    34. // 空目录, 直接返回
    35. return;
    36. }
    37. //遍历每一个文件
    38. for (File f : files) {
    39. if (f.isDirectory()) {
    40. // 是目录, 就进行递归
    41. scanDir(f, toDelete);
    42. } else {
    43. // 普通文件
    44. tryDelete(f, toDelete);
    45. }
    46. }
    47. }
    48. public static void tryDelete(File f, String toDelete) {
    49. // 看看当前文件名是否包含了 toDelete, 如果包含, 就删除, 否则就啥都不干
    50. if (f.getName().contains(toDelete)) {
    51. try {
    52. System.out.println("是否要删除文件(Y/n): " + f.getCanonicalPath());
    53. Scanner scanner = new Scanner(System.in);
    54. String choice = scanner.next();
    55. if (choice.equals("Y")) {
    56. f.delete();
    57. }
    58. } catch (IOException e) {
    59. e.printStackTrace();
    60. }
    61. }
    62. }
    63. }

    运行程序之前:

    运行程序: 


    此时,可以观察到结果:

     

    4.2 复制普通文件

    基本思路:

    把文件1 复制成 文件2,就是 把文件1 里面的内容 都按照字节读取出来,写入到 文件2 中 ~

    1. package file;
    2. import java.io.*;
    3. import java.util.Scanner;
    4. public class Demo14 {
    5. // 实现复制文件的功能
    6. public static void main(String[] args) {
    7. // 准备工作
    8. Scanner scanner = new Scanner(System.in);
    9. System.out.println("请输入要复制的文件路径: ");
    10. String srcPath = scanner.next();
    11. File srcFile = new File(srcPath);
    12. if (!srcFile.exists()) {
    13. System.out.println("要复制的文件不存在!");
    14. return;
    15. }
    16. if (!srcFile.isFile()) {
    17. System.out.println("要复制的不是普通文件!");
    18. return;
    19. }
    20. System.out.println("请输入要复制到的目标路径: ");
    21. String destPath = scanner.next();
    22. File destFile = new File(destPath);
    23. if (destFile.exists()) {
    24. System.out.println("要复制到的目标已经存在! ");
    25. return;
    26. }
    27. // 进行拷贝工作
    28. try (InputStream inputStream = new FileInputStream(srcFile)) {
    29. try (OutputStream outputStream = new FileOutputStream(destFile)) {
    30. byte[] buf = new byte[1024];
    31. while (true) {
    32. int len = inputStream.read(buf);
    33. if (len == -1) {
    34. // 拷贝完成
    35. break;
    36. }
    37. outputStream.write(buf, 0, len);
    38. }
    39. }
    40. } catch (IOException e) {
    41. e.printStackTrace();
    42. }
    43. System.out.println("复制完成!");
    44. }
    45. }

     运行程序之前:

    运行过程:

     运行结果:

     ​​​​​​

    4.3 获取含有指定字符串的普通文件

    1. package file;
    2. import java.io.File;
    3. import java.io.FileInputStream;
    4. import java.io.IOException;
    5. import java.io.InputStream;
    6. import java.util.Scanner;
    7. public class Demo15 {
    8. // 遍历目录, 看某个输入的词是否在文件名或者文件内容中存在.
    9. public static void main(String[] args) throws IOException {
    10. Scanner scanner = new Scanner(System.in);
    11. System.out.println("请输入要搜索的目录:");
    12. String rootPath = scanner.next();
    13. File rootFile = new File(rootPath);
    14. if (!rootFile.exists()) {
    15. System.out.println("要扫描的目录不存在!");
    16. return;
    17. }
    18. if (!rootFile.isDirectory()) {
    19. System.out.println("要扫描的路径不是目录!");
    20. return;
    21. }
    22. System.out.println("请输入要搜索的词:");
    23. String toFind = scanner.next();
    24. // 递归遍历目录
    25. scanDir(rootFile, toFind);
    26. }
    27. private static void scanDir(File rootFile, String toFind) throws IOException {
    28. File[] files = rootFile.listFiles();
    29. if (files == null) {
    30. return;
    31. }
    32. for (File f : files) {
    33. if (f.isDirectory()) {
    34. scanDir(f, toFind);
    35. } else {
    36. tryFindInFile(f, toFind);
    37. }
    38. }
    39. }
    40. // 判定 toFind 是否是文件名 或者 是文件内容的一部分
    41. private static void tryFindInFile(File f, String toFind) throws IOException {
    42. // 是不是文件名的一部分
    43. if (f.getName().contains(toFind)) {
    44. System.out.println("找到文件名匹配的文件: " + f.getCanonicalPath());
    45. return;
    46. }
    47. // 是不是文件内容的一部分
    48. try (InputStream inputStream = new FileInputStream(f)) {
    49. // 把文件内容整个的都读出来
    50. StringBuilder stringBuilder = new StringBuilder();
    51. Scanner scanner = new Scanner(inputStream);
    52. while (scanner.hasNextLine()) {
    53. stringBuilder.append(scanner.nextLine());
    54. }
    55. // 读取完毕
    56. if (stringBuilder.indexOf(toFind) >= 0) {
    57. System.out.println("找到文件内容匹配的文件: " + f.getCanonicalPath());
    58. return;
    59. }
    60. }
    61. }
    62. }

    打算查找 如下的文档,其中 6.txt 和 8.txt 有相同的内容 "aaa",其余文档均没有内容 ~

    运行结果:

    好了,关于 文件操作和IO 的内容就介绍到这里了,已经完美撒花啦 ~

    如果感觉这一篇博客对你有帮助的话,可以一键三连走一波,非常非常感谢啦 ~

  • 相关阅读:
    oracle 如何使用存储函数
    量子时代加密安全与区块链应用的未来
    阿里云智能总裁张建锋:保护客户数据安全是第一原则
    一些负载均衡算法
    C++函数知识点(增强版)
    QGIS编译(跨平台编译)之五十二:qgis_analysis库在Qt Creator环境下编译的错误处理
    【每日一题】782. 变为棋盘
    【数据结构初阶-复杂度】运行 只用了3ms...我真牛(得意
    2023最新SSM计算机毕业设计选题大全(附源码+LW)之java高校饭堂管理系统8gmjo
    Docker的网络与容器资源的控制
  • 原文地址:https://blog.csdn.net/qq_53362595/article/details/126691931