当前位置: 首页 > article >正文

【javaEE】文件操作--io

1.❤️❤️前言~🥳🎉🎉🎉

Hello, Hello~ 亲爱的朋友们👋👋,这里是E绵绵呀✍️✍️。

如果你喜欢这篇文章,请别吝啬你的点赞❤️❤️和收藏📖📖。如果你对我的内容感兴趣,记得关注我👀👀以便不错过每一篇精彩。

当然,如果在阅读中发现任何问题或疑问,我非常欢迎你在评论区留言指正🗨️🗨️。让我们共同努力,一起进步!

加油,一起CHIN UP!💪💪

🔗个人主页:E绵绵的博客
📚所属专栏:

1. JAVA知识点专栏

        深入探索JAVA的核心概念与技术细节

2.JAVA题目练习

        实战演练,巩固JAVA编程技能

3.c语言知识点专栏

        揭示c语言的底层逻辑与高级特性

4.c语言题目练习

        挑战自我,提升c语言编程能力

5.Mysql数据库专栏

        了解Mysql知识点,提升数据库管理能力

6.html5知识点专栏

        学习前端知识,更好的运用它

7. css3知识点专栏

        在学习html5的基础上更加熟练运用前端

8.JavaScript专栏

        在学习html5和css3的基础上使我们的前端使用更高级、

9.JavaEE专栏

        学习更高阶的Java知识,让你做出网站

📘 持续更新中,敬请期待❤️❤️

2.认识文件 

文件路径 

文件是对于"硬盘"数据的一种抽象,在一台计算机上,有非常多的文件,这些文件是通过 "文件系统" 来进行组织的,本质上就是通过 "目录"(文件夹) 这样的树形结构来组织文件的,画个图理解一下:

 

有了目录,我们就可以使用目录的层次结构来描述文件所在的位置,即 "路径"。如:"E:\cs2\5EClient",在这里还有两个概念:

绝对路径:以 c:d:盘符开头的路径,这种路径就是 "绝对路径"。
相对路径:需要指定一个目录作为基准目录,从基准目录出发,到达指定的文件,这里的路径就是 "相对路径",其中有三个特殊的符号:

  1. .\

    • 表示当前目录

    • 例如:.\file.txt 指的是当前目录中的 file.txt 文件。(注意 .\ 可以忽视不写)

  2. \

    • 表示下一级,用于分隔目录层级。

    • 例如:C:\Users\Username\Documents 中的 \ 用于分隔不同的目录层级,表示下一级

  3. ..\

    • 表示上一级目录(父目录)。

    • 例如:..\file.txt 指的是上一级目录中的 file.txt 文件。

这里给出一个相对路径示例:

.\Projects\..\Documents\Reports\2023\..\Summary\Final\report.txt 

通过上述讲述规律我们可以简化为

Documents\Reports\Summary\Final\report.txt

如果你搞懂这个逻辑是可以很快把它简化出来的,搞不懂的话那就好好自己琢磨一下。 

 文件类型

文件主要分为两大类:

1.文本文件:文件中保存的内容必须要求都是合法字符(计算机存储的数据都是二进制的,能通过字符编码将二进制数据转换成字符的就是合法字符)

2.二进制文件:文件中保存的数据可以存在不合法的数据(计算机存储的数据都是二进制的,不能通过字符编码将二进制数据转换成字符的就是不合法字符)

区分文本文件和二进制文件:将文件直接使用记事本打开,如果是乱码,就是二进制文件,如果不是,就是文本文件。

但是也存在一种情况:用记事本打开二进制文件不存在乱码(二进制文件并不是必须要求存在不合法的数据),所以这种情况分辨不出来,我们就通过后缀名去分辨:

常见的文本文件后缀名包括:

  1. .txt

    • 最常见的纯文本文件格式,通常用于存储简单的文本内容。

    • 示例:notes.txt

  2. .csv

    • 逗号分隔值文件,用于存储表格数据。

    • 示例:data.csv

  3. .html

    • 超文本标记语言文件,用于网页内容。

    • 示例:index.html

  4. .xml

    • 可扩展标记语言文件,用于存储结构化数据。

    • 示例:config.xml

  5. .json

    • JavaScript 对象表示法文件,用于存储和传输结构化数据。

    • 示例:settings.json

  6. .log

    • 日志文件,通常记录系统或应用程序的运行信息。

    • 示例:error.log

  7. .md

    • Markdown 文件,用于编写格式化的文本。

    • 示例:README.md


常见的二进制文件后缀名包括:

  1. .exe

    • 可执行文件,通常用于 Windows 应用程序。

    • 示例:program.exe

  2. .dll

    • 动态链接库文件,包含可由多个程序共享的代码和数据。

    • 示例:library.dll

  3. .jpg / .jpeg

    • 图像文件,采用 JPEG 压缩格式。

    • 示例:photo.jpg

  4. .png

    • 便携式网络图形文件,支持无损压缩和透明背景。

    • 示例:image.png

  5. .mp3

    • 音频文件,采用 MPEG 音频压缩格式。

    • 示例:song.mp3

  6. .mp4

    • 视频文件,采用 MPEG-4 压缩格式。

    • 示例:video.mp4

  7. .zip

    • 压缩文件,用于存储多个文件或文件夹。

    • 示例:archive.zip

  8. .pdf

    • 便携式文档格式文件,通常用于存储格式化文档。

    • 示例:document.pdf

  9. .bin

    • 通用的二进制文件,通常用于存储原始二进制数据。

    • 示例:data.bin

这里还有一个特殊的后缀名:.data 文件可以是二进制文件,也可以是纯文本文件,具体取决于创建它的程序。

3.Java中操作文件——File 

FILE针对文件系统进行操作,如创建文件, 删除文件, 创建目录,重命名文件....

 File的属性

E:\cs2\5EClient 中的 \ 就是 pathSeparator,如果当前的系统是 Windows,\ 或者 / 都可以作为分隔符,如果系统是 Linux 或 Mac ,只能使用 / 作为分隔符,一般我们都建议使用 / 作为分隔符,因为 \ 一般还需要搭配转义字符来使用,而且因为linux也是/,如果在windows中用/可以直接跨系统。

File的构造方法 

以下是 File 类三种构造方法:


1. File(String pathname)

直接通过相对路径或者绝对路径创建 File 实例。

File file = new File("C:/Users/Username/Documents/example.txt");

2. File(String parent, String child)

通过父目录路径和孩子文件路径创建 File 实例。

File file = new File("C:/Users/Username/Documents", "example.txt");

3. File(File parent, String child)

通过父目录 File 对象和孩子文件路径创建 File 实例。

File parentDir = new File("C:/Users/Username/Documents");
File file = new File(parentDir, "example.txt");

File的方法 

以下是 File 类常用方法的简单示例:


1. getParent()

返回父目录路径。

File file = new File("C:/Users/Username/Documents/example.txt");
System.out.println("父目录: " + file.getParent()); // 输出: C:/Users/Username/Documents

2. getName()

返回文件或目录的名称。(文件包含后缀,目录没有后缀)

System.out.println("文件名称: " + file.getName()); // 输出: example.txt

3. getPath()

返回文件或目录的路径。(可能是相对路径,也可能是绝对路径,具体取决于文件对象是通过相对还是绝对创建的。)

System.out.println("文件路径: " + file.getPath()); // 输出: C:/Users/Username/Documents/example.txt

4. getAbsolutePath()

返回绝对路径。(不经修饰的)

System.out.println("绝对路径: " + file.getAbsolutePath()); // 输出: 
 C:\Users\Username\Documents\..\Downloads\.\example.txt(这就为不经修饰的路径)

5. getCanonicalPath()

返回修饰后的绝对路径。

System.out.println("规范路径: " + file.getCanonicalPath()); // 输出: C:/Users/Username/Downloads/example.txt(根据方法四的例子可知这为修饰后的路径,更易读了)

6. exists()

检查文件或目录是否存在。(对于file对象对应的路径可以是虚拟的,不在真实文件系统中存在)

System.out.println("文件是否存在: " + file.exists()); // 输出: true 或 false

7. isDirectory()

判断是否为目录。

System.out.println("是否是目录: " + file.isDirectory()); // 输出: true 或 false

8. isFile()

判断是否为普通文件。

System.out.println("是否是文件: " + file.isFile()); // 输出: true 或 false

9. createNewFile()

创建新文件。(要求File对象必须是文件,不能是目录。 并且对于该路径中的各级目录必须都存在,否则不能创建)

boolean created = file.createNewFile();
System.out.println("文件是否创建成功: " + created); // 输出: true 或 false

10. delete()

删除文件或目录。

boolean deleted = file.delete();
System.out.println("文件是否删除成功: " + deleted); // 输出: true 或 false

11. deleteOnExit()

标记文件,在 JVM 退出时删除,不会立刻删除。

file.deleteOnExit();

12. list()

返回目录下的文件名称和目录名称的数组。

String[] files = file.list();
for (String name : files) {
    System.out.println(name);
}

13. listFiles()

返回目录下的 File 对象数组。

File[] files = file.listFiles();
for (File f : files) {
    System.out.println(f.getName());
}

14. mkdir()

创建单个目录。(尝试创建D: /path/to/singleDirectory 目录,如果中间目录不存在,则不能创建成功)​​​​​​​

boolean created = file.mkdir();
System.out.println("目录是否创建成功: " + created); // 输出: true 或 false

15. mkdirs()

创建多级目录。(尝试创建D: /path/to/singleDirectory 目录,如果中间目录不存在,则把不存在的中间目录以及目标目录都创建出来)

boolean created = file.mkdirs();
System.out.println("目录是否创建成功: " + created); // 输出: true 或 false

16. renameTo(File dest)

重命名文件或目录。(要求两个File对象的父路径都必须一样)

File dest = new File("C:/Users/Username/Documents/newName.txt");
boolean renamed = file.renameTo(dest);
System.out.println("文件是否重命名成功: " + renamed); // 输出: true 或 false

17. canRead()

检查文件是否可读。(文件会有可读可写权限)

System.out.println("文件是否可读: " + file.canRead()); // 输出: true 或 false

18. canWrite()

检查文件是否可写。(文件会有可读可写权限)

System.out.println("文件是否可写: " + file.canWrite()); // 输出: true 或 false

4.Java中文件内容读写——数据流 

关于文件内容操作:读文件,写文件,打开文件,关闭文件。

在Java 中通过"流"(stream 流)这样的一组类,进行上述的文件内容操作。

数据流的分类  

数据流根据文件类型也分成了两种:

1.字节流:对应二进制文件,每次读写的最小单位是 "字节"

2.字符流:对应文本文件,每次读写的最小单位是 "字符",英文的字符都是一个字节,一个汉字在不同的字符编码中是不同的字节大小,在 utf8 是 3 个字节,在 unicode 是 2 个字节。(字符流本质上是针对字节流进行的一层封装)

JAVA针对读写两种操作,分别为字节流提供了 InputStream(输入) 和 OutputStream(输出) 类,为字符流提供了 Reader(输入) 和 Writer(输出) 类。

这里有一个注意点,如何区分输入和输出:

以cpu为主视角,输入就是输入一些数据到cpu中(这些数据有可能是读取的数据),所以读取是输入流。

以cpu为主视角,输出就是将cpu中的数据放到别的地方(这些数据有可能是会放到硬盘中),所以写就是输出流。

 字符流的读写

 Reader  

Reader是一个抽象类,我们需要用FileReader去实例化。

下面是构造方法:

FileReader(File file) 利用 File 构造文件输入流

FileReader(String name) 利用文件路径构造文件输入流

之后的三个类的构造方法我们就不讲了,跟这个一摸一样,套模板

1. int read()

读取一个字符,返回其 Unicode 编码(两个字节大小)。如果到达流的末尾,返回 -1

这里有个问题,为什么是返回unicode编码呢?不能是utf8编码呢?因为在 Java 标准库内部, 对于字符编码是进行了很多的处理工作的,如果是只使用 char,此时使用的字符集固定就是 unicode,如果是使用 String, 此时就会自动的把每个字符的 unicode 转换成 utf8。

那为什么字符串不能用Unicode呢?非要转换?当我们把多个 unicode 连续放到一起,是很难区分出从哪里到哪里是一个完整的字符的.而utf8 是可以做到区分的,所以字符串就是utf8编码,单个字符就是unicode编码。


2. int read(char[] cbuf)

读取多个字符,尽量填满 cbuf 数组,返回实际读取的字符数。如果到达流的末尾,返回 -1

特别注意:这个方法不会关心数组 cbuf 中是否已经存在数据,它会直接将读取的字符从数组的起始位置(索引 0)开始覆盖写入。所以如果上次读满了数组,下次再进行读写会直接覆盖上次读写的数据(注意这种类型的读方法都是直接覆盖的,之后再出现这种类似的读方法我就不再特别说明了,比如下面的方法)


3. int read(char[] cbuf, int off, int len)

从 off 下标开始,读取最多 len 个字符到 cbuf 数组,返回实际读取的字符数。如果到达流的末尾,返回 -1


Writer

它是一个抽象类,我们需要用FileWriter去实例化。

构造方法跟上面一样。

注:默认情况下,在创建文件输出流时会将文件中原有的内容清空,这是在你创建输出流对象时发生的,一旦文件输出流创建完成,你就可以多次调用 write 方法来写入数据,而不会影响文件中已有的内容(调用write后并不会将内容清空,只有创建输出流时才会清空)

所以我们可以在构造方法最后一个参数里加一个 true , 在创建文件输出流时就不会将文件中原有的内容清空(适用于所有输出流)

字节流的读写

InputStream

同样用 fileinputstream 实例化对象,构造方法同理。 

它跟reader几乎一个样,不同的是它返回的是一个字节,另一个是字符。

由于是一个字节,所以我们采用十六进制表达,返回出两位数,更加好观察比十进制。

outputstream  

同样用 fileoutputstream 实例化对象,构造方法同理 

import java.io.*;
 
public class Demo5 {
    public static void main(String[] args) {
        try(OutputStream outputStream = new FileOutputStream("d:/text.txt",true)){
            String s = "你好世界";
            outputStream.write(s.getBytes());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

 它是输入流,所以同理,想要在该文件追加数据需要在构造方法后加个true。

close()方法 

在讲完这四个经典流之后,还要注意一个问题:

当我们使用这些输入流输出流打开文件后,一个文件如果使用完了,要记得close,使用 close 方法,最主要的目的是为了释放文件描述符。 

文件描述符我们之前在讲解pcb时讲过,每个打开的文件都有一个唯一的文件描述符,它存储在顺序表中,一个进程每次用输入流输出流打开一个文件,就需要在这个表里分配一个文件描述符,而这个数组的长度是存在上限的,如果你的代码,一直打开文件,而不去关闭文件,就会使这个表里的文件描述符,越来越多,一直到把这个数组占满了,后续再尝试打开其他文件,就会出错了,导致系统崩溃,这也叫做文件资源泄露,非常类似于内存泄露。

那么我们的close方法应该放在哪里呢?正常随便放置吗? 如下代码

import java.io.FileReader;

public class SimpleFileReaderExample {
    public static void main(String[] args) {
        try {
             FileReader reader  = new FileReader("example.txt");
            int data;
            while ((data = reader.read()) != -1) {
                System.out.print((char) data);
                reader.close();
            }
        } catch (Exception e) {
            System.out.println("发生错误: " + e.getMessage());
        } 
                
            }
        }
}

看到这个代码中带有捕获异常,我们额外说一点,对于文件io读写的代码通常都会有很多受查异常,所以一般在文件io代码中都要处理ioException异常,要么throws,要么trycatch,否则编译不成功,跟线程代码一样,通常都要处理异常。

那么回归正题,如果这么写,一旦在还没close时抛出异常或者系统崩溃,那么就执行不了close,就可能导致问题发生,所以我们都是将它放在finally中。

import java.io.FileReader;

public class SimpleFileReaderExample {
    public static void main(String[] args) {
        try {
             FileReader reader  = new FileReader("example.txt");
            int data;
            while ((data = reader.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (Exception e) {
            System.out.println("发生错误: " + e.getMessage());
        } finally {
                reader.close();
            }
        }
    }
}

 但这看起来还是不太美观,为此我们引用了一个try的语法,将代码变为如下代码:

​
​
import java.io.FileReader;

public class SimpleFileReaderExample {
    public static void main(String[] args) {
        try(FileReader reader  = new FileReader("example.txt")) {
            int data;
            while ((data = reader.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (Exception e) {
            System.out.println("发生错误: " + e.getMessage());
        }
    }
    }
}

​

​

它是try-with-resources 语句,这是一种更简洁且自动管理资源(如文件流、网络连接、数据库连接等)的方法。

try (ResourceType resource = new Resource()) {
    // 使用资源的代码
} catch (ExceptionType e) {
    // 异常处理代码
}

这里要求resource必须实施closable接口(有close方法),当用了该语法后,我们就不用搞一个finally里面包含close的代码,这语法里面自动隐藏包含着finally里面包含close的代码,可以达到一样的效果,并且更优美,以后我们写的类似数据流代码都是跟该格式一样。

Scanner

除了用我们已知的的read方法读取输入流中的字符,我们还可以用Scanner去读取输入流里的文本数据。

public class Demo6 {
    public static void main(String[] args) {
        try (FileReader reader = new FileReader("d:/a.txt")){
            Scanner scanner = new Scanner(reader);
            String s = scanner.next();
            System.out.println(s);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}


从这个scanner语法结构我们发现一个很重要的事,对于之前的scanner(system.in)也是一样的语法结构,没错其实system.in也是一个输入流,通过用户键盘去输入数据,而后通过scanner读取system.in输入流的数据。

printwiter 

除了通过write去写外,我们还可以通过printwiter去操作:

public class Demo7 {
    public static void main(String[] args) {
        try(OutputStream outputStream = new FileOutputStream("d:/a.txt")){
            PrintWriter writer = new PrintWriter(outputStream);
            writer.println("你好世界");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

在使用了之后,我们却发现并没有添加成功,这是为什么呢?

因为 PrintWriter 这个类,在进行写入操作的时候,不一定直接写入硬盘,而是先把数据写入一个内存中的空间,叫做 "缓冲区"。为什么会出现缓冲区?因为把数据写入内存,是非常快的,而把数据写入硬盘,是非常慢的(比内存慢几千倍甚至更多),为了提高效率,我们选择降低写硬盘的次数,等积累多了之后,再一次写进去。这样就会出现问题,我们将数据写入 "缓冲区" 后,还没有将缓冲区的数据写入硬盘,进程就结束了,此时数据就丢失了,也就会出现上述图片中的问题。

解决方案是什么呢?确保数据能完整的写入硬盘,我们需要手动的用 flush() 方法刷新缓冲区:

public class Demo7 {
    public static void main(String[] args) {
        try(OutputStream outputStream = new FileOutputStream("d:/a.txt")){
            PrintWriter writer = new PrintWriter(outputStream);
            writer.println("hello world");
            writer.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}


以后我们出现类似情况就有可能是缓存区遗留了信息,没及时传上去,要记得用flush及时刷新缓存区。

那么缓冲区会在我们基本的四个类里出现吗? 

不会,它出现在BufferedReader和BufferedWriter中,printwiter之所以有缓冲区是因为底层是BufferedWriter类,所以才有缓冲区。


http://www.kler.cn/a/583985.html

相关文章:

  • 使用mybatis-plus自定义分页实现一对多的分页功能
  • Unity引擎架构介绍及代码示例
  • Nature最新报道:分析四大主流AI工具、性能测评、推荐使用场景
  • Vim忍者速成秘卷:让你的键盘冒出残影の奥义
  • 如何通过ibd文件恢复MySql数据
  • 鸿蒙编译框架插件HvigorPlugin接口的用法介绍
  • 蓝桥杯备考:数据结构堆之 除2!
  • STM32Cubemx-H7-9-串口接受不定长度数据并识别
  • 解决 VSCode SSH 连接报错:“REMOTE HOST IDENTIFICATION HAS CHANGED” 的问题
  • Nginx 多协议代理功能(Nginx Multi Protocol Proxy Function)
  • windows11 LTSC 24h2 访问NAS问题的安全高效解决
  • C语言:计算并输出三个整数的最大值 并对三个数排序
  • 图解AUTOSAR_CP_ServiceDiscovery
  • Unix 域套接字(本地套接字)
  • NLP常见任务专题介绍(4)-ConditionalGeneration和CasualLM区别
  • 关于Playwright和Selenium 的区别和选择
  • nginx部署使用【常用命令】
  • C++时间复杂度详解
  • Blackbox.Ai体验:AI编程插件如何提升开发效率
  • Docker 基础命令 - 以 Nginx 实战总结