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

【文件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. 读文件

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

image.png

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. 写文件

image.png|449

在文件中写入“你好

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. 读文件

为了方便处理字符,引入字符流
image.png|518

一次读一个字符:

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

小结:
当前设计的这八个类,虽然数目不少,但用法都很相似
基本流程:打开 —> 读写 —> 关闭

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • jmeter使用while控制器时防止死循环
  • 临床数据科学和金融数据科学,选择R语言吧!
  • Python操作MongoDB文档存储
  • workerman下的webman路由浏览器跨域的一种问题
  • Docker详解
  • sh脚本发送邮件到多个收件人如何高效实现?
  • #Datawhale AI夏令营第4期#AIGC方向 文生图 Task2
  • 前端面试题整理-Javascript
  • 凤凰端子音频矩阵应用领域
  • 【问题解决】git status中文文件名乱码
  • #laravel部署安装报错loadFactoriesFrom是undefined method #
  • WordPress原创插件:Download-block-plugin下载按钮图标美化
  • 力扣面试经典算法150题:罗马数字转整数
  • 【JavaEE初阶】线程池
  • [LitCTF 2024]exx
  • [笔记] php常见简单功能及函数
  • AzureCon上微软宣布了哪些容器相关的重磅消息
  • iOS帅气加载动画、通知视图、红包助手、引导页、导航栏、朋友圈、小游戏等效果源码...
  • Koa2 之文件上传下载
  • NSTimer学习笔记
  • SpiderData 2019年2月13日 DApp数据排行榜
  • 代理模式
  • 每天10道Java面试题,跟我走,offer有!
  • 那些年我们用过的显示性能指标
  • 如何解决微信端直接跳WAP端
  • nb
  • ​sqlite3 --- SQLite 数据库 DB-API 2.0 接口模块​
  • ## 1.3.Git命令
  • #pragma预处理命令
  • #传输# #传输数据判断#
  • #微信小程序:微信小程序常见的配置传旨
  • (2)Java 简介
  • (55)MOS管专题--->(10)MOS管的封装
  • (pojstep1.3.1)1017(构造法模拟)
  • (博弈 sg入门)kiki's game -- hdu -- 2147
  • (补充):java各种进制、原码、反码、补码和文本、图像、音频在计算机中的存储方式
  • (附程序)AD采集中的10种经典软件滤波程序优缺点分析
  • (数据结构)顺序表的定义
  • (四)进入MySQL 【事务】
  • (四)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (太强大了) - Linux 性能监控、测试、优化工具
  • (一)springboot2.7.6集成activit5.23.0之集成引擎
  • (转)GCC在C语言中内嵌汇编 asm __volatile__
  • ***监测系统的构建(chkrootkit )
  • .[hudsonL@cock.li].mkp勒索加密数据库完美恢复---惜分飞
  • .naturalWidth 和naturalHeight属性,
  • .NET Framework .NET Core与 .NET 的区别
  • .net on S60 ---- Net60 1.1发布 支持VS2008以及新的特性
  • .NET 表达式计算:Expression Evaluator
  • .NET构架之我见
  • .NET正则基础之——正则委托
  • @angular/cli项目构建--Dynamic.Form
  • @RequestParam,@RequestBody和@PathVariable 区别
  • @TableLogic注解说明,以及对增删改查的影响
  • [ IOS ] iOS-控制器View的创建和生命周期