【文件IO】文件内容操作

简介: 【文件IO】文件内容操作

读文件、写文件,都是操作系统提供了 API,在 Java 中也进行了封装,叫“文件流”/“IO流

Stream

流,形象比喻,水流/气流


水流的特点:我要通过水龙头,接 1000ml 水

  1. 直接一口气,把 1000ml 接完
  2. 一次接 500ml,分两次接完
  3. 一次接 100ml,分十次接完

IO 流的特点:我要从文件读取 100 字节文件

  1. 直接一口气,把 100 字节读完
  2. 一次读 50 字节,分两次读
  3. 一次读 10 字节,分十次

操作系统本身提供的文件读写 API 就是流式

Java 实现 IO 流,类有很多,主要分为两个大类:

字节流和字符流

  • 字节流:二进制文件使用
  • 读写数据的基本单位,就是字节
  • 一次读的 bit 不可少于 8 个,因为一个字节 8 个 bit,至少得读一个字节

表示字节流的类

  • InputStream,用来输入的
  • OutputStream,用来输出的
  • 字符流:文本文件使用
  • 一个字符不确定有几个字节,取决于实际的编码方式(GBK—一个汉字两个字节、UTF 8—一个汉字三个字节,一个字母一个字节
  • 内部做的工作更多,会自动的查询码表,把二进制数据转换成对应字符

表示字符流的类

  • Reader,输入
  • Writer,输出

比如,就像读取某个文件中的前 10 个汉字

使用字符流就可以非常方方便的实现

  • 直接读取 10 个字符
  • 字符流自动判定文件是哪种编码方式,再将字节分割好
  • 再读取对数量字节就得到 10 个汉字了

理解清楚“输入/输出”的方向(人为定义的)

把内存中的数据,放到硬盘上,视为输入还是输出呢?

  • 如果站在内存视角,就是输出
  • 如果站在硬盘视角,就是输入

后面但凡谈到输入输出,都是以 CPU 的视角来谈的,内存离 CPU 比硬盘离 CPU 更近

  • 数据远离 CPU,就是输出,将内存中的数据写到硬盘里
  • 数据靠近 CPU,就是输入,硬盘文件中的数据拿到内存里

上面四个输入输出的类,都是“抽象类

实际上真正干活的,并非这四个类

另外,Java 中,提供了很多很多类,实现上述的这四个抽象类

因为类太多了,就使得我们对于 IO 流的理解就非常费劲

  • 但虽然种类多,但其实大家的用法都差不多
  • 但凡类的名字是以“Read/Writer”结尾的,就是实现了 ReadWriter 的字符流对象
  • 但凡类的名字是以“InputStream/OutputStream”结尾的,就是实现了 InputStreamOutputStream 的字节流对象
import java.io.FileInputStream;  
import java.io.IOException;  
import java.io.InputStream;  
  
public class Demo8 {  
    //这个异常是IOException的子类,是其特殊的情况,可以直接写成 IOException    public static void main(String[] args) throws IOException {  
        //因为他是一个抽象类,所以不能直接new  
        //只能new一个实现了它的子类  
        InputStream inputStream = new FileInputStream("./text.txt");  
        //可以指定绝对路径,也可以指定相对路径,也可以指定 File 对象  
        inputStream.close();  
    }
}
  • FileNotFoundException 这个异常是 IOException 的子类,是他的一种特殊情况,可以就 throws 这个父类异常
  • 抽象类不能直接被new,只能new一个实现了它的子类
  • 在这里还隐藏了一个操作,“打开文件”,针对文件进行读写,务必需要先打开(操作系统的基本要求)
  • 指定路径的时候,可以指定绝对路径,也可以指定相对路径,也可以指定 File 对象
  • 这个代码中,虽然要求文件使用完毕之后要关闭,但是局限于本代码,不写close也行。因为close之后,紧接着就是进程结束了
  • close 是释放“文件描述符表”里的元素,进程结束,意味着 PCB 就销毁了,PCB 上面的文件描述符表就整个释放了

文件资源泄露

打开文件之后,还需要关闭文件

打开文件,其实是在该进程的文件描述符表中,创建了一个新的表项

  • 进程 => PCB(进程控制块)=> 文件描述表
  • 这个表描述了该进程都需要操作哪些文件
  • 可以认为它是一个数组,数组的每个元素就是一个 struct file 对象(Linux 内核)
  • 每个结构体就描述了对应操作的文件的信息
  • 数组的下标,就称为“文件描述符

每次打开一个文件,就相当于在数组上占用一个位置,而在系统内核中,文件描述附表数组是固定长度&不可扩容的。除非主动调用 close 关闭文件,此时才会释放空间。如果代码里一直打开,不去关闭,就会使这里的资源越来越少,把数组填满了,后续再打开文件就会打开失败

这样的问题,不容易被发现,泄露不是一瞬间就泄露完耳朵,这是一个持续的过程。整个问题直到所有的资源泄露完毕,这一刻才会集中的爆发出来

import java.io.FileInputStream;  
import java.io.IOException;  
import java.io.InputStream;  
  
public class Demo8 {  
    public static void main(String[] args) {  
        try(InputStream inputStream = new FileInputStream("./text.txt")){  
                    }catch(IOException e){  
            e.printStackTrace();  
        }    
    }
}
  • close 的时候,问了防止因为一些特殊原因代码执行不到 close,有一种特殊的 try 方法——try with sources
  • 这里() 中的创建的资源可以是多个
  • try{}执行完毕,最终都会自动执行这里的close
  • 不过想在() 里面写,必须是实现了 Closable 接口的类
    `

字节流

1. 读文件

在文件打开之后,就需要读文件了

import java.io.FileInputStream;  
import java.io.IOException;  
import java.io.InputStream;  
  
public class Demo8 {  
    public static void main(String[] args) {  
        try(InputStream inputStream = new FileInputStream("./text")){  
            while (true) {  
                int b = inputStream.read();  
                if(b == -1){  
                    //读取完毕  
                    break;  
                }                
                System.out.printf("0x%x\n",b);  
            }        
        }catch(IOException e){  
            e.printStackTrace();  
        }    
    }
}
//运行结果(text文件内容:hello)
0x68
0x65
0x6c
0x6c
0x6f
//(text文件内容:你好)
0xe4
0xbd
0xa0
0xe5
0xa5
0xbd
  • 当读到最后一个字节,就返回 -1
  • 打印字节的时候,一般都用十六进制进行表示,方便随时换算成二进制
  • hello,可在 ASCII 码表中找到对应单词;“你好”因为是六个字节,所以可以确定是 UTF8 编码方式,就可以在 UTF8 码表中对应打印出的内容拼出“你好”

频繁读取多次硬盘,当前硬盘的 IO 就耗时比较大,希望能减少 IO 的次数

byte[] buffer = new byte[1024];  
int n = inputStream.read(buffer);
  • 这个操作就会把硬盘中读取到的对应的数据,填充到buffer内存的字节数组中,并且尽可能填满(只需要一次IO
  • 此处是把 buffer 形参当成了“输出型参数”
  • 平时写代码,方法的参数一般是“输入型参数”,使用返回值表示输出结果
  • 虽然是一次读的内容多了,但也比一次读 1 个字节,分很多次读效率高不少
  • 返回的 n 代表实际读到的字节数

  • 这个过程也非常类似于“去食堂打饭”
  • 拿空盘递给阿姨打饭(空 bufferread
  • 阿姨打满后,再把盘给你(read 把读完的内容装进 buffer

import java.io.FileInputStream;  
import java.io.IOException;  
import java.io.InputStream;  
  
public class Demo9 {  
    public static void main(String[] args) {  
        try(InputStream inputStream = new FileInputStream("./text")){  
            byte[] buffer = new byte[1024];  
            int n = inputStream.read(buffer);  
            for (int i = 0; i < n; i++) {  
                System.out.printf("0x%x\n",buffer[i]);  
            }        
        }catch (IOException e){  
            e.printStackTrace();  
        }    
    }
}

和 Scanner 结合:

import java.io.FileInputStream;  
import java.io.IOException;  
import java.io.InputStream;  
import java.util.Scanner;  
  
public class Demo14 {  
    public static void main(String[] args) throws IOException{  
        try(InputStream inputStream = new FileInputStream("./text")){  
            Scanner scanner = new Scanner(inputStream);  
            while(scanner.hasNextInt()){  
                System.out.println(scanner.nextInt());  
            }        
        }catch (IOException e){  
            e.printStackTrace();  
        }    
    }
}
  • 这样也可以完成文件内容的读取

2. 写文件

在文件中写入“你好

import java.io.*;  
  
public class Demo10 {  
    public static void main(String[] args) throws IOException {  
        try(OutputStream outputStream = new FileOutputStream("./text");){  
            outputStream.write(0xe4);  
      outputStream.write(0xbd);  
      outputStream.write(0xa0);  
      outputStream.write(0xe5);  
      outputStream.write(0xa5);  
      outputStream.write(0xbd);
        }catch (IOException e){  
            e.printStackTrace();  
        }    
    }
}
  • 这里是按照一个字节一个字节的方式进行写入的
  • 每次执行写操作的时候,都会先把之前的内容清空
  • 只要使用 OunputStream 打开文件,文件里面的内容就没了
  • 这样的操作,可能就把文件内容搞没了,并且找不回来了

还有一种“追加写”的方式,保持原内容不变,在末尾写入新内容

try(OutputStream outputStream = new FileOutputStream("./text",true);)
  • 在最后加上一个参数 true,代表开启“追加写”的方式

一次把整个字节数组都写入:

import java.io.*;  
  
public class Demo10 {  
    public static void main(String[] args) throws IOException {  
        try(OutputStream outputStream = new FileOutputStream("./text",true);){  
            byte[] buffer = new byte[] {(byte)0xe4,(byte)0xbd,(byte)0xa0,(byte)0xe5,(byte)0xa5,(byte)0xbd};  
            outputStream.write(buffer);  
        }catch (IOException e){  
            e.printStackTrace();  
        }  
    }  
}

InputStream / OutputStream 读写数据就是按照字节来操作的。如果要读写字符的话(中文),此时就绪要靠程序员手动来区分出哪几个字节是一个字符,再确保把这几个字节作为整体来写入

字符流

1. 读文件

为了方便处理字符,引入字符流

一次读一个字符:

import java.io.FileReader;  
import java.io.IOException;  
import java.io.Reader;  
  
public class Demo11 {  
    public static void main(String[] args) throws IOException {  
        try(Reader reader = new FileReader("./text")){  
            while (true) {  
                int c = reader.read();  
                if (c == -1) return;  
                char ch = (char) c;  
                System.out.println(ch);  
            }        }catch (IOException e){  
            e.printStackTrace();  
        }    
    }
}
  • 每次 read 读到的就是一个汉字
  • 最初按照字节来读的时候,是每个汉字三个字节,但在Java中一个char是两个字节,怎么用两个字节表示出了一个汉字?
  • 当使用 char 表示这里的汉字的时候,不再使用 UTF8,而是使用 unicode 编码方式
  • unicode 中,一个汉字就是两个字节
  • 使用字符流读取数据的过程,Java 标准库内部就自动针对数据的编码进行转码了

用字符数组一次读若干字符:

import java.io.FileReader;  
import java.io.IOException;  
import java.io.Reader;  
  
public class Demo12 {  
    public static void main(String[] args) throws IOException {  
        try(Reader reader = new FileReader("./text")){  
            char[] buffer = new char[1024];  
            int n = reader.read(buffer);  
            System.out.println(n);  
            for (int i = 0; i < n; i++) {  
                System.out.println(buffer[i]);  
            }        
        }catch (IOException e){  
            e.printStackTrace();  
        }    
    }
}

2. 写文件

import java.io.FileWriter;  
import java.io.IOException;  
import java.io.Writer;  
import java.nio.channels.WritableByteChannel;  
  
public class Demo13 {  
    public static void main(String[] args) throws IOException {  
        try(Writer writer = new FileWriter("./text")){  
            writer.write("你好世界");  
        }catch (IOException e){  
            e.printStackTrace();  
        }    
    }
}
  • 直接写入一个 String 到文件中

小结:

当前设计的这八个类,虽然数目不少,但用法都很相似

基本流程:打开 —> 读写 —> 关闭


相关文章
|
1月前
|
搜索推荐 索引
【文件IO】实现:查找文件并删除、文件复制、递归遍历目录查找文件
【文件IO】实现:查找文件并删除、文件复制、递归遍历目录查找文件
34 2
|
1月前
|
存储 Java API
【文件IO】文件系统操作
【文件IO】文件系统操作
40 1
|
2月前
|
Java 大数据 API
Java 流(Stream)、文件(File)和IO的区别
Java中的流(Stream)、文件(File)和输入/输出(I/O)是处理数据的关键概念。`File`类用于基本文件操作,如创建、删除和检查文件;流则提供了数据读写的抽象机制,适用于文件、内存和网络等多种数据源;I/O涵盖更广泛的输入输出操作,包括文件I/O、网络通信等,并支持异常处理和缓冲等功能。实际开发中,这三者常结合使用,以实现高效的数据处理。例如,`File`用于管理文件路径,`Stream`用于读写数据,I/O则处理复杂的输入输出需求。
|
1月前
|
存储 Java 程序员
【Java】文件IO
【Java】文件IO
35 0
|
2月前
|
Linux C语言
C语言 文件IO (系统调用)
本文介绍了Linux系统调用中的文件I/O操作,包括文件描述符、`open`、`read`、`write`、`lseek`、`close`、`dup`、`dup2`等函数,以及如何获取文件属性信息(`stat`)、用户信息(`getpwuid`)和组信息(`getgrgid`)。此外还介绍了目录操作函数如`opendir`、`readdir`、`rewinddir`和`closedir`,并提供了相关示例代码。系统调用直接与内核交互,没有缓冲机制,效率相对较低,但实时性更高。
|
3月前
|
存储 监控 Linux
性能分析之从 IO 高定位到具体文件
【8月更文挑战第21天】性能分析之从 IO 高定位到具体文件
41 0
性能分析之从 IO 高定位到具体文件
|
3月前
IO流拷贝文件的几种方式
IO流拷贝文件的几种方式
36 1
|
4月前
|
Linux 数据处理 C语言
【Linux】基础IO----系统文件IO & 文件描述符fd & 重定向(下)
【Linux】基础IO----系统文件IO & 文件描述符fd & 重定向(下)
75 0
|
5月前
|
C++
Open3D File Io 文件IO
Open3D File Io 文件IO
|
4月前
|
Linux C语言 C++
【Linux】基础IO----系统文件IO & 文件描述符fd & 重定向(上)
【Linux】基础IO----系统文件IO & 文件描述符fd & 重定向(上)
53 0