• 文件操作 和 IO - 详解


    一,认识文件

    1.1 树形结构组织和目录

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

    有了目录,我们就可以使用目录的层次结构来描述文件所在的位置,即 "路径"。如:D:\Program Files (x86)\编程3\Common\VSPerfCollectionTools\vs2022\1033,在这里还有两个概念:

    • 绝对路径:以 C:D:盘符开头的,这种路径就是 "绝对路径"。
    • 相对路径:需要指定一个目录作为基准目录,从基准目录出发,到达指定的文件,这里的路径就是 "相对路径"。这些路径往往是以  . (代表当前目录) 或者  .. (代表当前目录的上一级目录) 开头的。

    1.2 文件类型

    文件主要分为两大类:

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

    2)二进制文件:文件中保存的数据是二进制数据,即不是合法的字符

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

    二,文件操作 - FILE

    2.1 属性

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

    E:\01\MSDN 中的 \ 就是 pathSeparator,如果当前的系统是 Windows,\ 或者 / 都可以作为分隔符,如果系统是 Linux 或 Mac ,只能使用 / 作为分隔符,一般建议使用 / 作为分隔符,因为 \ 一般还需要搭配转义字符来使用。

    2.2 构造方法

    构造方法说明
    File(File parent, String child)
    根据父目录 + 孩子文件路径,创建一个新的 File 实例
    File(String pathname)
    根据文件路径创建一个新的 File 实例,路径可以是绝对路径或者 相对路径
    File(String parent, String child)
    根据父目录 + 孩子文件路径,创建一个新的 File 实例,父目录用路径表示

    2.3 方法

    返回值类型方法名说明
    StringgetParent()
    返回 File 对象的父目录文件路径
    StringgetName()
    返回 FIle 对象的纯文件名称
    StringgetPath()
    返回 File 对象的文件路径
    StringgetAbsolutePath()
    返回 File 对象的绝对路径
    StringgetCanonicalPath()
    返回 File 对象的修饰过的绝对路径
    booleanexits()
    判断 File 对象描述的文件是否真实存在
    booleanisDirectory()
    判断 File 对象代表的文件是否是一个目录
    booleanisFile()
    判断 File 对象代表的文件是否是一个普通文件
    booleancreateNewFile()
    根据 File 对象,自动创建一个空文件。成功创建后返
    true
    booleandelete()
    根据 File 对象,删除该文件。成功删除后返回 true
    voiddeleteOnExit()
    根据 File 对象,标注文件将被删除,删除动作会到
    JVM 运行结束时才会进行
    String[]
    list()
    返回 File 对象代表的目录下的所有文件名
    File[]
    listFiles()
    返回 File 对象代表的目录下的所有文件,以 File 对象表示
    boolean
    mkdir()
    创建 File 对象代表的目录
    boolean
    mkdirs()创建 File 对象代表的目录,如果必要,会创建中间目录
    boolean
    renameTo(File dest)
    进行文件改名,也可以视为我们平时的剪切、粘贴操
    boolean
    canRead()
    判断用户是否对文件有可读权限
    boolean
    canWirte()
    判断用户是否对文件有可写权限
    1. public class Demo {
    2. public static void main(String[] args) throws IOException {
    3. File file = new File("./text.txt");//不要求该文件一定存在
    4. System.out.println(file.getParent());
    5. System.out.println(file.getName());
    6. System.out.println(file.getPath());
    7. System.out.println(file.getAbsolutePath());
    8. System.out.println(file.getCanonicalPath());
    9. }
    10. }

    1. import java.io.File;
    2. import java.io.IOException;
    3. import java.util.Arrays;
    4. public class Demo1 {
    5. public static void main(String[] args) throws IOException {
    6. File file = new File("d:/text.txt");
    7. System.out.println(file.exists());//false
    8. System.out.println(file.isDirectory());//false
    9. System.out.println(file.isFile());//false
    10. System.out.println(file.createNewFile());//true
    11. System.out.println(file.delete());//true
    12. //file.deleteOnExit();在程序全部执行完之后删除文件
    13. File file1 = new File("d:/");
    14. String[] ret = file1.list();
    15. System.out.println(Arrays.toString(ret));
    16. File file2 = new File("d:/aaa/bbb/ccc");
    17. boolean ans = file2.mkdirs();//能创建多级目录
    18. //file2.mkdir();只能创建一级目录,如 d:/aaa
    19. System.out.println(ans);
    20. }
    21. }

    三,文件内容读写 - 数据流

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

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

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

    JAVA针对读写两种操作,分别为字节流提供了 InputStream(输入) 和 OutputStream(输出) 类,为字符流提供了 Reader(输入) 和 Writer(输出) 类。这里有一个注意点,如何区分输入和输出,画个图:

     3.1 字符流 - Reader

    返回值类型方法名说明
    intread()从文件中读取一个字符,返回unicode编码
    intread(char[] cbuf)从文件中读取若干字符,将cbuf数组填满,返回实际读取的字符数
    intread(chae[] cbuf, int off, int len)从文件中读取作干字符,从off下标开始,长度为len的cbuf数组填满,返回实际读取的字符数
    1. import java.io.FileReader;
    2. import java.io.IOException;
    3. import java.io.Reader;
    4. public class Demo3 {
    5. public static void main(String[] args) throws IOException {
    6. //一次读一个字符
    7. Reader reader = new FileReader("d:/text.txt");//打开文件
    8. while(true){
    9. int n = reader.read();//读取一个字符
    10. if(n == -1){//返回-1表示文件读取完毕
    11. break;
    12. }
    13. char ch = (char) n;
    14. System.out.println(n);
    15. }
    16. reader.close();
    17. }
    18. }

    但是这么写还是可能会出现文件资源泄露,如果在while循环中抛出异常,下面的close()方法就执行不到了,所以我们可以使用 try...finally..来实现:

    1. import java.io.FileReader;
    2. import java.io.IOException;
    3. import java.io.Reader;
    4. public class Demo3 {
    5. public static void main(String[] args) throws IOException {
    6. //一次读多个字符
    7. Reader reader = new FileReader("d:/text.txt");//打开文件
    8. try{
    9. while(true){
    10. char[] ret = new char[10];
    11. int n = reader.read(ret);
    12. if(n == -1) break;
    13. for (int i = 0; i < n; i++) {
    14. System.out.println(ret[i]);
    15. }
    16. }
    17. }finally {
    18. reader.close();//关闭操作
    19. }
    20. }
    21. }

    这么写虽然解决了问题,但是不够方便,在这里还有一种写法:

    1. public class Demo3 {
    2. public static void main(String[] args) throws IOException {
    3. //只有实现closeable接口才可以这样写(流对象都可以)
    4. try(Reader reader = new FileReader("d:/text.txt")){
    5. while(true){
    6. char[] ret = new char[10];
    7. int n = reader.read(ret);
    8. if(n == -1) break;
    9. for (int i = 0; i < n; i++) {
    10. System.out.println(ret[i]);
    11. }
    12. }
    13. }
    14. }
    15. }

    3.2 字符流 - Writer

    方法名说明
    write(int c)一次写一个字符
    write(String str)一次写多个字符
    write(char[] cbuf)一次写多个字符,使用字符数组
    write(String str, int off, int len)从下标off开始往文件中写入,长度为len
    write(char[] cbuf, int off, int len)从下标off开始往文件中写入,长度为len

    注:默认情况下,写入文件会将文件中的原有内容清空。

    1. import java.io.FileWriter;
    2. import java.io.IOException;
    3. import java.io.Writer;
    4. public class Demo4 {
    5. public static void main(String[] args) {
    6. try(Writer writer = new FileWriter("d:/text.txt")) {
    7. writer.write("原神,启动!");//写入,先清空再写入
    8. } catch (IOException e) {
    9. throw new RuntimeException(e);
    10. }
    11. /* 在构造方法参数中加一个 true , 就可以直接在文件后面填写,不需要清空
    12. try(Writer writer1 = new FileWriter("d:/text.txt",true)) {
    13. writer1.write("原神,启动!");//写入
    14. } catch (IOException e) {
    15. throw new RuntimeException(e);
    16. }*/
    17. }
    18. }

    3.3 字节流 - InputStream

    返回值类型方法名说明
    intread()
    读取一个字节的数据,返回 -1 代表已经完全读完了
    intread(byte[] b)
    最多读取 b.length 字节的数据到 b 中,返回实际读到的数量;-1 代表以及读完了
    int
    read(byte[] b, int off, int len)
    最多读取 len - off 字节的数据到 b 中,放在从 off 开始,返回实际读到的数量;-1 代表以及读完了
    void
    close()
    关闭字节流
    1. import java.io.*;
    2. public class Demo5 {
    3. public static void main(String[] args) {
    4. try(InputStream inputStream = new FileInputStream("d:/text.txt")) {
    5. byte[] buffer = new byte[10];
    6. while (true){
    7. int n = inputStream.read(buffer);
    8. if(n == -1) break;
    9. for (int i = 0; i < n; i++) {
    10. System.out.printf("%x\n",buffer[i]);
    11. }
    12. }
    13. }catch (IOException e) {
    14. throw new RuntimeException(e);
    15. }
    16. }
    17. }

    3.4 字节流 - OutputStream

    返回值类型方法名说明
    voidwrite()
    写入要给字节的数据
    voidwrite(byte[] b)
    b 这个字符数组中的数据全部写入  
    int
    write (byte[] b, int off, int len)
    b 这个字符数组中从 off 开始的数据写入 ,一共写 len
    void
    close()
    关闭字节流
    voidflush()
    大多的 OutputStream 为 了减少设备操作的次数,在写数据的时候都会将数据先暂时写入内存的 一个指定区域里,直到该区域满了或者其他指定条件时才真正将数据写 入设备中,这个区域一般称为缓冲区。但造成一个结果,就是我们写的 数据,很可能会遗留一部分在缓冲区中。需要在最后或者合适的位置, 调用 flush (刷新操作,将数据刷到设备中。
    1. import java.io.*;
    2. public class Demo5 {
    3. public static void main(String[] args) {
    4. try(OutputStream outputStream = new FileOutputStream("d:/text.txt",true)){
    5. String s = "哈哈哈哈";
    6. outputStream.write(s.getBytes());
    7. } catch (IOException e) {
    8. throw new RuntimeException(e);
    9. }
    10. }
    11. }

    3.5 字节流转字符流

    当别人传给你的是一个字节流文件,但是你知道实际数据内容是文本数据时,我们可以通过以下方法来实现转换:

    1. import java.io.FileInputStream;
    2. import java.io.IOException;
    3. import java.io.InputStream;
    4. import java.util.Scanner;
    5. public class Demo6 {
    6. public static void main(String[] args) {
    7. try (InputStream inputStream = new FileInputStream("d:/text.txt")){
    8. Scanner scanner = new Scanner(inputStream);
    9. String s = scanner.next();
    10. System.out.println(s);
    11. } catch (IOException e) {
    12. throw new RuntimeException(e);
    13. }
    14. }
    15. }

     

    1. import java.io.*;
    2. public class Demo7 {
    3. public static void main(String[] args) {
    4. try(OutputStream outputStream = new FileOutputStream("d:/text.txt")){
    5. PrintWriter writer = new PrintWriter(outputStream);
    6. writer.println("fsaf");
    7. } catch (IOException e) {
    8. throw new RuntimeException(e);
    9. }
    10. }
    11. }

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

    为了解决该问题,确保数据能完整的写入硬盘,我们需要手动的用 flush() 方法刷新缓冲区:

    1. import java.io.*;
    2. public class Demo7 {
    3. public static void main(String[] args) {
    4. try(OutputStream outputStream = new FileOutputStream("d:/text.txt")){
    5. PrintWriter writer = new PrintWriter(outputStream);
    6. writer.println("fsaf");
    7. writer.flush();
    8. } catch (IOException e) {
    9. throw new RuntimeException(e);
    10. }
    11. }
    12. }

  • 相关阅读:
    【Python大数据】PySpark
    【云原生之K8S】K8S管理工具kubectl 详解
    msvcp140.dll文件下载方法,找不到msvcp140.dll丢失的解决方法
    Vue13 监视属性
    三维模型3DTile格式轻量化的纹理压缩和质量关系分析
    Docker镜像制作
    餐厅预订APP多少钱一套?餐厅预订APP如何收费?
    计算机加减乘除的本质
    记一次 .NET 某工控自动化控制系统 卡死分析
    【STM32】入门(六):I2C协议
  • 原文地址:https://blog.csdn.net/m0_74859835/article/details/133562207