本文主要是针对文件内容的操作进行展开,文件内容操作无非就两种
1.针对文件进行“读” 2.针对文件进行“写”
目录
文件内容读写的形式
字符流
字节流
文件内容操作
InputStream:以字节流的形式进行读操作
创建方式:
FileInputStream的构造方法:
常用的构造方法:
读操作read:
这里的read有三个版本
OutputStream:以字节流的方式进行写操作
创建对象
写操作:write
字符流读写操作:Reader和Writer
Reader:以字符流的形式进行读文件操作
Writer:以字符流的形式进行写文件操作
关闭文件资源释放相关
不释放资源的后果
针对文件资源泄露的情况,我们有下面两种方法:
1. 手动调用close方法进行资源关闭
2. try with resources 写法:
为什么能自动释放:
文件内容读写的形式
字符流
在进行文件内容操作时,读写的基本单位是“字符”
每次读写至少都是一个字符
在文件内容操作,专门提供了一组针对字符流操作的类:Reader,Writer……
字节流
在进行文件内容操作时,读写的基本单位是“字节”
每次读写至少都是一个字节
在文件内容操作,专门提供了一组针对字节流操作的类:InputStream,OutputStream……
文件内容操作
InputStream:以字节流的形式进行读操作
创建方式:
原码:
我们看到InputStream类是一个abstract修饰的抽象类,不能直接实例化,只能通过它的非抽象子类来进行对象的创建
正确的创建方式:
InputStream inputStream = new FileInputStream();
由于我们需要进行的文件操作,所以此时创建的是针对文件的InputStream:FileInputStream
FileInputStream的构造方法:
常用的构造方法:
1. 以File对象作为参数,对该对象指定的文件内容进行操作
2. 以文件路径(绝对路径/相对路径)作为参数,对指定文件的内容进行操作
注意:需要抛出异常:FileNotFoundException:如果操作的文件不存在就会抛出该异常
例如:
InputStream inputStream = new FileInputStream("test1/test.txt");
此时就建立了InputStream对象inputStream,通过这个对象与当前项目路径下的test1文件夹下的test.txt文件进行了绑定,下面就可以通过inputStream这个对象来对test.txt进行操作了
读操作read:
由于这个test.txt文件中没有数据,所以我们手动写入一个hello world
通过调用read方法
这里使用我们上面例子中创建的inputStream对象
inputStream.read();
注意:read一次返回的是一个字节,但是返回类型是一个int类型
因为:一个byte类型的数据范围是-128-127(有符号),或者0-255(无符号)这里使用的范围是0-255,
read说明文档:如果返回-1那么说明文件已经读完了,因为读完了并没有读取到需要返回的字节,所以也不能返回范围内的数据,此时就规定读完返回-1
原码解释说明:
这里的read有三个版本
1. 无参的read版本:read()
从文件开头开始读,每次读一个字节
try(InputStream inputStream = new FileInputStream("test1/test.txt")){
//.....这里面进行inputStream这个文件对象的文件操作
while(true){
int b = inputStream.read();
if(b == -1){
//当read的返回值为-1时,说明文件数据已经被读完了
break;
}
System.out.println(b);
}
}
结果:
此时输出的结构并不是hello world ,而是一段数字,这里也是非常科学的
因为结果输出为read的返回值,也就是test.txt中每个字节内容的返回值
解析:
通过ASCll码解析一下我们就能得到,104对应的就是‘h’,101对应的是:‘e’,所以这些数字对应下来正好是我们test.txt中的内容"hello world"
注意:如果文件中的内容是中文,返回的也会是数字,但是此时数组就不一定是ascll码了,因为在计算机中的中文是由字符集编码表示,所以如果内容是中文的话,需要通过对应的字符集编码去反解析到对应的中文
System.out.print((char)b;//通过char强转成字符类型显示
结果:
2. 带一个byte数组的参数版本:read(byte[] b)
带有一个byte[] b的参数版本读取本质和上面其实是一样的,只不过给read读取到的结果指定了一个容器数组b,所以我们通过这个版本的read去读取数据,结果会放到b数组中
3. 带三个参数的版本:read(byte[] b , int off , int len)
这个又是在2的基础上增加了一个开始读取的位置(int off),增加了一个读取的字节长度(int len):意思就说从off的偏移量处开始读取,读取len个字节长度,把读取的结构放到b中
OutputStream:以字节流的方式进行写操作
操作和前面的InputStream基本一样
创建对象
和InputStream一样OutputStream也是一个abstract修饰的抽象类,所以也不能直接实例对象,而针对文件的写操作,需要使用子类FileOutputStream
OutputStream outputStream = new FileOutputStream("test1/test.txt")
FileOutputStream常见的构造方法
1. 以File对象作为参数,对该对象指定的文件内容进行操作
2. 以文件路径(绝对路径/相对路径)作为参数,对指定文件的内容进行操作
注意:需要抛出异常:FileNotFoundException:如果操作的文件不存在就会抛出该异常
写操作:write
使用上面创建的outputStream对象调用write方法
outputStream.write();
write方法也有几种版本
1. 无参版本:write(int b)
调用一次write,一次写入一个字节
try(OutputStream outputStream = new FileOutputStream("test1/test.txt")){
outputStream.write(98);
}
通过outputStream对象往绑定的test.txt中写入一个值为98的字节,那么98对应到的是b字符
此时我们的test.txt中的内容就是b了
注意:
不是同一次项目执行的写入,会更新掉文件里面的内容,每次执行程序写入都是重新写入
解释:
如果第一次执行程序往test.txt中写入了一个a,此时test.txt文件中的内容就是a了
第二次执行程序往text.txt中写入一个b,此时test.txt文件中的内容就只有b了,a被清除了
2. 带有一个byte数组参数的版本:write(byte[] b)
这个版本的write是进行读字节数组,调用这个write则把b数组中的字节元素都读入到指定文件中
3. 带三个参数的版本:write(byte[] b , int off , int len)
这个版本与2差不多相同,只不过在那个基础上加上了数组偏移量(off)和写入的长度(len),
就是从b数组的off处元素开始写入进文件,写入的元素个数为len
注意:由于InputStream和OutputStream都是是字节流形式的I/O操作类,所以不只是可以进行文件内容的读写操作,在网络编程是也可以通过InputStream和OutputStream对网卡进行读写操作
字符流读写操作:Reader和Writer
Reader:以字符流的形式进行读文件操作
Reader以字符流的形式对文件进行读操作,操作过程和方法和InputStream基本一样,
都是调用read方法,里面的参数和参数的含义也一样,同样也是读到末尾返回-1,使用时可对照前面的InputStream使用
代码:
try(Reader reader = new FileReader("test1/test.txt")){
while(true){
int b = reader.read();
if(b == -1){
break;
}
System.out.println((char)b);
}
} catch (IOException e) {
e.printStackTrace();
}
Writer:以字符流的形式进行写文件操作
Writer以字符流的形式对文件进行写操作,操作过程和方法和OutputStream基本一样,
都是调用write方法,里面的参数和参数的含义也一样,使用时可对照前面的OutputStream使用
代码:
try(Writer writer = new FileWriter("test1/test.txt")) {
writer.write('a');
writer.write('b');
writer.write('c');
} catch (IOException e) {
e.printStackTrace();
}
结果:
关闭文件资源释放相关
不释放资源的后果
注意:我们可以理解为InputStream和OutputStream是在与硬盘中的文件进行绑定,相当于遥控器,通过创建的对象就能对硬盘中的绑定文件进行读写操作,
此时也相当于c语言中的打开文件操作,有打开文件操作,那么为了防止文件资源泄露,则必然需要关闭文件,在操作完文件对象之后,我们需要关闭(解除)绑定:调用close方法。
这里泄露的文件资源:主要是文件描述符
注意:
我们都知道操作系统在对进程进行管理时(PCB),会记录当前进程访问的硬盘资源,从而生成一条条文件描述符,组成当前进程的文件描述符表,但是PCB的文件描述符表数组长度是有限的,如果我们不进行手动关闭文件去 释放资源的话,当文件描述符表数组被占满后,进程就不能打开新的文件了,会导致进程无法正常工作甚至是崩溃,从而造成非常严重的Bug
针对文件资源泄露的情况,我们有下面两种方法:
1. 手动调用close方法进行资源关闭
inputStream.close();
2. try with resources 写法:
带有资源的try操作,会在try代码块结束,会自动对资源执行close操作
写法:
把创建InputStream等这些的I/O组类对象时,放入try中创建,再在try代码块中进行对象的文件文件操作,在退出代码块时会自动释放try中创建的I/O资源(推荐写法:自动释放不会遗忘更稳妥)
try(InputStream inputStream = new FileInputStream("test1/test.txt")){
//.....这里面进行inputStream这个文件对象的文件操作
}
此时当执行完try里面的文件操作后,退出try时会自动释放inputStream对象的资源
为什么能自动释放:
因为InputStream和OutPutStream都实现了Closeable接口
实现了Closeable接口的类,就可以使用try with resources语法,然后try在代码块结束后就会自动调用close