文件IO(Java)
注:此博文为本人学习过程中的笔记
1.概念
狭义上的文件是指保存在硬盘上的文件,广义上指操作系统进行资源管理的一种机制,很多软件/硬件资源都可以抽象成文件,这里我们针对的是狭义上的文件。
在硬盘里还有文件夹,这是通俗的说法,我们一般称其为目录。
2.硬盘,内存和寄存器
1.区别
存储空间
硬盘很大(几TB),内存更小(几十GB),寄存器非常小(不到1KB)
访问速度
硬盘很慢,内存快,寄存器很快
成本
硬盘便宜,内存贵,寄存器不单独卖,很贵
2.硬盘
硬盘分为固态硬盘和机械硬盘,机械硬盘受限于内部结构,速度非常慢。
3.路径
一台计算机中能够保存的文件有很多,那么要怎么识别唯一的文件呢?此时就需要用到路径。路径就是定位到文件的一系列过程。在计算机中,目录套目录形成了树形结构,从树根开始到最终的文件,都需要经过哪些目录,就形成路径。
1.使用
一般用“/”(正斜杠)表示。主流操作系统中都是使用/表示,windows比较特殊,使用“\”(反斜杠)表示。不过windows里是兼容正斜杠的,所以我们都使用正斜杠。在编写代码的时候,正斜杠可以直接使用,反斜杠需要转义。
2.相对路径和绝对路径
1.绝对路径
从盘符开始逐级表示
2.相对路径
相对路径需要明确一个基准,使用.表示基准的位置。使用..表示基准的上一层。使用../..表示基准的上两层。
在代码中写一个相对路径,它的基准是不确定的。如果在IDEA上运行,基准就是项目的目录。如果打一个jar包,单独运行jar包,那么是在哪个目录执行运行命令,基准就是哪个目录。
4.文件的种类
从开发的角度把文件分成两类,二进制文件和文本文件。图片,视频,压缩包之类的都是二进制文件。
所有的文件都是二进制文件,有一些文件是特殊的,二进制数据刚好能构成一些字符,能狗仔码表查到,并且构成有意义的内容。判断文本文件的方法很简单,直接用记事本打开看即可。注意word里的docx是二进制文件,里面不只有文本,还有格式,图表之类的东西。
5.Java标准库中操作文件的类
1.文件系统操作
创建文件,删除文件,重命名,创建目录
File - Java17中文文档 - API参考文档 - 全栈行动派 (qzxdp.cn)
进行文件系统操作时使用File这个类。里面的方法大多比较简单,查看文档即可,这里只写一些需要注意的点。
1.方法注意点
1.list()和listFile()
这个方法的作用是返回当前目录的子元素有哪些,需要注意的是它是无法获得子元素的子元素或孙子元素的,如果想,需要我们通过代码来实现。list返回的是字符串,而listFile返回的是File类,可以进行更多操作。
2.renameTo()
这个方法的作用是重命名目录,对于操作系统来说,重命名和移动本质上是一样的,因为定位文件是靠路径完成的。
2.文件内容操作
针对一个文件的内容进行读和写,通过一组“流对象”实现。流是操作系统层面的术语,和语言无关,其他语言操作文件内容也叫流。输入和输出是以cpu为参考的。
字节流读数据时是文件的原始数据。而字符流会根据文件内容的编码格式进行解析,比如可以把utf-8编码下3个字节的汉字解析后放到2个字节的char里。
其中的操作大部分是很相似的,这里就只详细介绍InputStream,其他都一笔带过。
1.字节流
读写文件以字节为单位,是针对二进制文件进行使用的。字节流主要有两个类InputStream和OutputStream,其他类都直接或间接继承这两个类。
1.InputStream
1.new操作
//注意InputStream是个抽象类
InputStream inputStream = new FileInputStream(文件路径);
这里的创建操作一旦成功,就认为打开了文件。要先打开文件才能进行操作,这是操作系统定义的流程。这个时候就不能忘记操作完成之后要关闭文件。
2.close()
inputStream.close();
不手动释放文件资源,就会引起文件资源泄露问题。 每次打开一个文件,就会在文件描述符表(固定长度的顺序表)中占据一个表项,如果光打开,不关闭,这里的文件描述符表的表项就会耗尽,后续再打开文件就会失败。文件操作附表是不能自动扩容的,操作系统内核里的操作是给所有进程提供的,如果能自动扩容会进一步增加内核的不可控因素。
close和unlock一样,容易因为各种原因漏掉,所以我们可以使用finally语句或者try语句的变种来保证close操作一定执行。
try {InputStream inputStream = new FileInputStream();
} finally {inputStream.close();
}//括号里可以new多个,在括号里new的东西会在try的代码块结束后自动调用close关闭
try(InputStream inputStream = new FileInputStream();) {}
//能自动关闭的类都实现了Closable接口
3.read()
int read()
调用一次,读取一个字节,返回的是int而不是byte,返回值是正常数字时对照码表查具体是什么,返回-1是表示文件已经读完
try(InputStream inputStream = new FileInputStream("./text.txt")) {//开始进行读文件操作while(true) {int data = inputStream.read();if(data == -1) {//如果读完就退出break;}System.out.println(data);}
}
int read(byte[] b)
一次读若干个字节,读取到的数据放到参数b中。
这个方法使用参数作为方法的返回值。一般来说,方法都是把参数作为需要加工的材料,把返回值当作生产出来的产品。有时也会使用参数来接收返回值,当参数是一个引用类型时,方法内部修改对象内容也能影响到方法外部。
这种"输出型参数"本质上是语法的限制,因为语法限制方法只能有一个返回值,如果希望返回多个数据,只能通过参数来凑。
try(InputStream inputStream = new FileInputStream("./text.txt")) {byte[] data = new byte[1024];while(true) {//read方法会尽可能读完数据,直到把data填满为止。int n = inputStream.read(data);//这里的n表示实际读了几个字节for(int i = 0; i < n; i++) {System.out.println(data[i]);}}
}
int read(byte[], int off, int len)
这里的off表示offset指的是偏移量,也可以理解成数组下标,len表示长度。这个方法和第二个类似,多了指定下标和长度。
2.OutputStream
1.new
对于OutputStream来说,默认情况下会尝试创建不存在的文件。打开文件的一瞬间是会清除上次文件的内容的,可以设置成追加写的模式,避免文件被清空。
OutputStream outputStream = new FileOutputStream("./text.txt", true);
2.write
void write(int b)
一次写一个字节
void write(byte[] b)
一次写若干个字节
void write(byte[] b, int off, int, len)
一次写若干个字节,可以指定下标和长度
2.字符流
读写文件以字符为单位,是针对文本文件进行使用的。字符流主要有两个类Writer和Reader,其他类都直接或间接继承这两个类。
1.Reader
1.read()
int read()
一次读一个字符
int read(char[] cbuf)
一次读一个字符数组
int read(CharBuffer target)
CharBuffer相当于对char[]进行了封装
int read(char[] cbuf, int off, int len)
一次读一个字符数组,可以指定下标和长度
2.Writer
write()
void write(int c)
void write(String str)
void write(char[] cubf)
void write(String str, int off, int len)
void write(char[] cubf, int off, int len)
3.提高效率的方法
1.手动创建缓冲区,手动减少read和write的次数
2.使用标准库提供的"BufferedStream"缓冲区流