文件IO实际就是针对文件内容进行操作,包括打开,读取/写入,关闭文件;I就是Input,O就是Output;
针对文件IO,就要说要“流”(stream)这个概念了;
说要流,我们就会想到水流,源源不断的水流,我们要接100ml的水,可以一次性接完,也可以两次接完,一次接50ml,还有很多种接法;
对于要读取100字节的数据,你可以选择一次性接100字节,也可以分两次,一次接50字节;这与接水非常相似,也可以叫文件流;
对于文件内容的操作,我们通常有两种方法:字节流和字符流;
字节流
就是以字节为单位从文件读取/写入数据到文件,一般用于二进制文件;
当要从文件读取数据时,使用InputStream抽象类,针对文件,有专门的类FileInputStream实现;
FileInputStream的构造方法
方法签名 | 说明 |
FileInputStream(String filePath) | 用文件的路径作为参数 |
FileInputStream(File file) | 用文件对象作为参数 |
通过以上两种构造方法打开文件,以便读取文件数据;
读取文件数据的三种主要方法
第一种 read()
读取数据的方法是每一次读取一个字节的数据,读到数据返回0-255,读到文件末尾返回-1;
看到这里你是否会有一个疑问,为什么要用int接收,而不是byte?
- 第一:为的是有足够的空间返回-1;
- 第二:确保读到的数都是正整数,实际上byte作为有符号整数的取值范围是-128-+127,但是作为字节,本身应该是无符号的,以确保取值范围可以是0-255,而用int接收,就是确保读到的都是正整数
- 第三:为什么不用short,而使用int呢?
- 建议大家忘记short,直接用int;就像用到float的时候,我们都会直接使用double,以提高精度;
第二种 read(byte[] )
读取的数据方式,就是要传入一个byte数组,文件数据都会尽量填满这个数组,填不满就意味着读到文件末尾了,返回读到的字节个数,返回-1就表示读到文件末尾了;
第三种 read(byte[] b,int off, int len)
方式就是第二种方式的改变,从off下标开始填入数据,读取len个字节,返回读到的字节个数,返回-1就表示读到文件末尾了;
最常用的是第二种方法,快捷方便,比较符合符合实际情况;每次读取一个字节是一个数组的开销是一样的,但是后者可以更快;
代码演示(文本内容是hello)
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class demo1 {
public static void main(String[] args) throws IOException {
InputStream inputStream=new FileInputStream("D:/test.txt");
byte[] read=new byte[1024];
while(true){
int len=inputStream.read(read);
System.out.println(len);
if(len==-1){
break;
}
for (int i = 0; i < len; i++) {
System.out.printf("0x%x ",read[i]);//十六进制
}
System.out.println();
}
}
}
写入数据到文件(字节流)
依旧是用FileOutputStream打开文件,实现OutputStream抽象类;
FileOutputStream的构造方法
方法签名 | 说明 |
FileOutputStream(String filePath,boolean append) | 用文件的路径作为参数,appned表示追加,true表示写入数据追加到文件末尾,false表示清除文件原有内容,进行覆盖写入 |
FileOutputStream(File file,boolean append) | 用文件对象作为参数,appned表示追加,true表示写入数据追加到文件末尾,false表示清除文件原有内容,进行覆盖写入 |
写入方法
相信只要看到参数,大概就能懂了,唯一需要注意就是第二种传入整数范围应该是0-255;对应ASCII码表;
代码演示
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class demo2 {
public static void main(String[] args) throws IOException {
OutputStream outputStream=new FileOutputStream("D:/test.txt",true);
for (int i = 0; i < 3; i++) {
outputStream.write(97);
outputStream.write(98);
outputStream.write(99);
outputStream.write(100);
}
}
}
字符流
以字符为单位读取文件数,写入数据到文件;
分别用到的是Reader读取和Writer写入抽象类,对于文件有FileReader和FileWriter的具体类实现数据读取和写入;
打开文件的构造方法与上面字节流的构造方法的雷同
区别就是写入和读取的方法签名不一样;
读取文件的方法签名
read(charBuffer target) ----->read(char[] target)
方法与字节流的方法相同,多了第三种方法,这个方法其实就是标准库把这个字符数组进行了一个封装,实际还是char[];
写入文件的方法签名
写入数据的方法虽然多了几种,但是也只是数据类型不一样;
值得注意的是,以字符流写入数据到文件的时候,需要用到两个方法,flush()和close(),不然会无法写入数据?
- FileWriter内置了一个缓冲区,会将要写入的数据先放置在缓冲区,如果不主动关闭文件流,就会使得jvm结束的时候把文件流强制关闭了,数据也就自然无法正常写入 ;
- 使用flush(),会将缓冲区的数据全部写入文件,文件流还能继续使用;
- 使用close(),回先将缓冲区的数据全部写入文件,然后关闭文件流,释放文件资源;
关闭文件
上面一直在讲述如何打开文件,读写文件,却一直没有说如何关闭文件,及时关闭文件流,释放资源是非常重要的。
关闭文件流的重要性
系统内核有一个文件描述表,专门用来存放打开的文件的相关信息的 ,主要是因为这个文件描述符表是有限制大小的,就意味着不能一直只是打开文件不释放文件,虽然程序结束,文件流会主动关闭,但是如果在大型的程序当中,没有及时关闭文件流,就会使得文件描述表被占满,以至于后续要打开文件都会失败,所以不及时不关闭文件流就是一个会随时爆炸的炸弹;
关闭文件的三种方法(有待补充)
第一种
最直接的方法,在使用完文件之后就调用close()关闭文件流。但是这也存在一定的弊端,如果在关闭之前,代码出现错误,救会使得close()方法无法被执行,导致文件无法关闭;
第二种
使用try--catch--finally格式,将close()放在finally中,这样就可以保证即使代码出现死循环也能够确保close()被执行到;
第三种
使用try with resource,代码如下
try(Reader reader=new FileReader("D:/test.txt")){
while(true){
int c=reader.read();
if(c==-1){
break;
}
char ch=(char)c;
System.out.println(ch);
}
}catch (IOException e){
e.printStackTrace();
}
将打开文件放在try后面,这个打开文件的方法,会附带主动关闭文件流; 如果使用第二种,你会发现代码有点长,第三种方法就是在第二种方法进行简化代码,同时也保证了不会忘记调用close();