【文件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 到文件中

小结:

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

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


相关文章
|
24天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
16天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
20天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2577 22
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
|
18天前
|
人工智能 IDE 程序员
期盼已久!通义灵码 AI 程序员开启邀测,全流程开发仅用几分钟
在云栖大会上,阿里云云原生应用平台负责人丁宇宣布,「通义灵码」完成全面升级,并正式发布 AI 程序员。
|
3天前
|
JSON 自然语言处理 数据管理
阿里云百炼产品月刊【2024年9月】
阿里云百炼产品月刊【2024年9月】,涵盖本月产品和功能发布、活动,应用实践等内容,帮助您快速了解阿里云百炼产品的最新动态。
阿里云百炼产品月刊【2024年9月】
|
2天前
|
存储 人工智能 搜索推荐
数据治理,是时候打破刻板印象了
瓴羊智能数据建设与治理产品Datapin全面升级,可演进扩展的数据架构体系为企业数据治理预留发展空间,推出敏捷版用以解决企业数据量不大但需构建数据的场景问题,基于大模型打造的DataAgent更是为企业用好数据资产提供了便利。
163 2
|
20天前
|
机器学习/深度学习 算法 数据可视化
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
2024年中国研究生数学建模竞赛C题聚焦磁性元件磁芯损耗建模。题目背景介绍了电能变换技术的发展与应用,强调磁性元件在功率变换器中的重要性。磁芯损耗受多种因素影响,现有模型难以精确预测。题目要求通过数据分析建立高精度磁芯损耗模型。具体任务包括励磁波形分类、修正斯坦麦茨方程、分析影响因素、构建预测模型及优化设计条件。涉及数据预处理、特征提取、机器学习及优化算法等技术。适合电气、材料、计算机等多个专业学生参与。
1576 16
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
|
22天前
|
编解码 JSON 自然语言处理
通义千问重磅开源Qwen2.5,性能超越Llama
击败Meta,阿里Qwen2.5再登全球开源大模型王座
977 14
|
4天前
|
Linux 虚拟化 开发者
一键将CentOs的yum源更换为国内阿里yum源
一键将CentOs的yum源更换为国内阿里yum源
221 2
|
17天前
|
人工智能 开发框架 Java
重磅发布!AI 驱动的 Java 开发框架:Spring AI Alibaba
随着生成式 AI 的快速发展,基于 AI 开发框架构建 AI 应用的诉求迅速增长,涌现出了包括 LangChain、LlamaIndex 等开发框架,但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言,并非十分友好和丝滑。因此,我们基于 Spring AI 发布并快速演进 Spring AI Alibaba,通过提供一种方便的 API 抽象,帮助 Java 开发者简化 AI 应用的开发。同时,提供了完整的开源配套,包括可观测、网关、消息队列、配置中心等。
734 9