前言 (红字部分要重点理解)
在上一篇文章中, 我们已经介绍过File类的一些用法了,这篇具体总结下。
Java代码对文件内容操作:标准库中提供了很多类,是一组类,而学习一个类,先从构造方法开始,如下图:
也就是说在构造File实例时,可以传的参数就是上图中的参数,可以是相对路径,也可以是绝对路径,如下代码:
package IO;
import java.io.File;
import java.io.IOException;
public class IODemo1 {
public static void main(String[] args) throws IOException {
//File file = new File("./美食.jpg"); //相对路径
File file = new File("d:/我的相册/美食.jpg");//绝对路径
System.out.println(file.getParent());
System.out.println(file.getName());
System.out.println(file.getPath());
System.out.println(file.getCanonicalPath());
}
}
运行结果:
Java代码在系统中创建一个文件: 如下代码,mkdir是创建目录(但是只能是创建一级目录),mkdirs是创建多级目录。
package IO;
import java.io.File;
public class IODemo3 {
public static void main(String[] args) {
File file = new File("text-dir/aaa/bbb");
file.mkdir();
//file.mkdir只能创建一级目录
//创建多级目录
file.mkdirs();
}
}
运行结果:
在创建好文件之后,还可以用list方法看一下当前目录中都有哪些文件,有两种显示方式,显示的效果是一样的,只是返回的参数不同,如下代码:
public static void main1(String[] args) {
//还可以显示文件里的内容 有两种显示方式
File file = new File("text-dir");
//list是以字符串数组的方式来显示
String[] results = file.list();
System.out.println(Arrays.toString(results));
//listFiles是以文件数组的方式显示的
File[] results2 = file.listFiles();
System.out.println(Arrays.toString(results2));
}
运行结果:
接下来就是Java操作文件内容了,标准库种提供了两种类,在介绍这两组类之前,要分清啥是输入和输出,认清楚方向: 下图解释了输入输出。
Java标准库中对操作文件内容提供了两组类:(上文已经说过普通文件的两种类型)
1.针对文本文件,提供了一组类,统称为“字符流” (典型代表:Reader,Writer)。
说到流(stream)这个字可能很抽象,我们可以想想一下水流,水流的特点就是源源不断,一直流完为止,可以类比到从水龙头中放水,接100ml水可以有很多种接法,可以是一次性接100ml,也可以一次接50ml,分两次来接,也可以一次接1ml,分100次来接,但是总是要有一个单位的,比如说水龙头一次最少流一滴水,接水时再也不能少过这个基本单位了。
字符流,基本单位就是每次最少读一个字符(这里的字符不仅仅是char类型,也可以是其他字符集的字符),也可以读多个字符(根据方法中的参数来决定),如下图,不带参数的方法默认就是读一个字符,
注:创建File实例的时候,分割每个目录最好用 “ / ” 来表示,如果是反斜杠表示,就需要写成“\\”,因为需要转义。
package IO;
import java.io.*;
public class IODemo5 {
public static void main(String[] args) {
try(OutputStream outputStream = new FileOutputStream("d:/text.txt")) {
outputStream.write(97);
outputStream.write(98);
outputStream.write(99);
//此时也是一次写一个byte read和write也可以一次读写多个字节
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main1(String[] args) throws IOException {
//InputStream是一个抽象类,所以不能直接new对象,相当于IO操作不只是可以读写硬盘,也可以读写网卡
//所以FileInputStream继承于InputStream,就说明此时是读系统文件
/*InputStream inputStream = new FileInputStream("d:/text.txt");
inputStream.close();//最后操作完文件内容一定要关闭资源,一定要记得!!很重要*/
//此处就不用手动关闭资源了,InputStream实现了closed接口,这种写法会自动的调用关闭资源的方法
try(InputStream inputStream =new FileInputStream("d:/text.txt")) {
//读文件
//read 一次返回的是一个字节
//但是不是用byte来接收,看源码有解释,用一个-1来表示已经读完文件了
//此处的返回值类型是int
while (true) {
int b = inputStream.read();
if (b == -1) {
break;
}
System.out.println(b);
//因为此时操作是字节流,所以输出的是对应的ASCII码值
}
}
}
}
看完上述代码,需要注意的点: (3)文件资源泄露是重点一定要重视!!
(1)OutputStream outputStream = new FileOutputStream("d:/text.txt") 这一行代码可以看到,在new一个对象时,并没有直接new OutputStream,原因就是它是一个抽象类(可以看下源码),所以需要new 继承OutputStream类的子类。为啥它是一个抽象类呢?OutputStream就是IO,所以不只是可以读写硬盘上的文件,还可以读写网卡等等,其中FileOutputStream(文件中读写)这个子类就明确的说明了IO是要从文件中进行读写。
(2)new一个对象在一个内存中,那咋样才能去读写硬盘中的文件呢,InputStream inputStream =new FileInputStream("d:/text.txt")这一行代码的意思就是打开文件,打开文件的意思就是让inputStream这个对象和硬盘中的("d:/text.txt")这个文件关联起来(就相当于有了一个遥控器(上一篇文章已经介绍过可以看下)),操作这个内存中的对象就相当于间接的操作硬盘中的文件了。
(3)有打开文件就得有关闭文件,这个还真需要咱们Java程序员注意!(当然我还不是程序猿),Java不像C++一样需要手动释放资源(像内存啥的都是手动释放的),一般GC会帮我们释放资源(垃圾回收器)但是在文件操作这里,是需要我们手动进行释放的,释放的这个资源就是文件描述符,文件描述符这个概念是在进程,进程是使用PCB这样的结构来进行描述的,这里面就包含各种信息:1.pid(进程id)2.内存指针(表示当前这个进程是在、哪一部分内存中) 3.文件描述符表 (这个文件描述符表中就是一个个的文件描述符,文件描述符就是标识了当前进程都打开了哪些文件,一个文件描述符就相当于这个表中的位置,位置记载了这个文件的信息,当打开一个文件时,就会在这里边申请一个位置,文件描述符表可以当成一个数组,数组下标就是一个文件描述符,下标对应的元素就是文件在内核中的结构体的表示。) 可是这个数组的大小终究是有限的,不能无限的放这样的一个一个的文件描述符,所以就不能无限制的打开一个个文件但是又不释放资源(就是数组的那个下标),一旦数组存的元素满了,再继续打开文件,就会打开失败 --> 这个操作就交文件资源泄露。一旦文件资源泄露之后,会有很严重的后果(甚至比C++的内存泄露还要严重!!)
但是又不用写很挫的代码,就是手动关闭文件,可以看上边代码:try(InputStream inputStream =new FileInputStream("d:/text.txt")) 用了这样的 写法,此时就不用手动关闭资源了,这个写法是try with resources(带资源的try操作) 它会在try中的代码执行完毕后自动帮助我们去关闭文件,为啥呢,我们可以看下图中的源码:OutputStream(InputStream也一样)实现了特定的Closeable接口,这个接口就是帮我们关闭文件的,也就相当于当try代码块结束后,就会自动帮助我们调用这个接口,进行文件的关闭,如果我们自己的代码实现了这个Closeable接口,也是可以用try with resources这样的语法来完成。
2.针对二进制文件,提供了一组类,统称为“字节流” (典型代表:InputStream,OutputStream)
而回到字节流中,就是从文件中读100个字节的数据,可以一次性读100个字节,也可以一次读50个字节,分两次来读,也可以一次读一个字节,分100次来读取。而字节流这个类中最小单位就是一个字节(就像是上文中的一滴水一样)。
package IO;
import java.io.*;
public class IODemo5 {
public static void main(String[] args) {
try(OutputStream outputStream = new FileOutputStream("d:/text.txt")) {
outputStream.write(97);
outputStream.write(98);
outputStream.write(99);
//此时也是一次写一个byte read和write也可以一次读写多个字节
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main1(String[] args) throws IOException {
//InputStream是一个抽象类,所以不能直接new对象,相当于IO操作不只是可以读写硬盘,也可以读写网卡
//所以FileInputStream继承于InputStream,就说明此时是读系统文件
/*InputStream inputStream = new FileInputStream("d:/text.txt");
inputStream.close();//最后操作完文件内容一定要关闭资源,一定要记得!!很重要*/
//此处就不用手动关闭资源了,InputStream实现了closed接口,这种写法会自动的调用关闭资源的方法
try(InputStream inputStream =new FileInputStream("d:/text.txt")) {
//读文件
//read 一次返回的是一个字节
//但是不是用byte来接收,看源码有解释,用一个-1来表示已经读完文件了
//此处的返回值类型是int
while (true) {
int b = inputStream.read();
if (b == -1) {
break;
}
System.out.println(b);
//因为此时操作是字节流,所以输出的是对应的ASCII码值
}
}
}
}
此处需要注意的点:
可能看完上述代码会有疑问:int b = inputStream.read() 无参数的read()不是一次读一个字节吗,为啥用int类型的变量来接收呢,我们可以看下源码:
这段英文就给了我们答案:
(1)一个字节(byte)可以表示整数的范围就是 -128 ~ +127,如果是无符号就是0 ~ 255,此时表示的范围是 0 ~ 255 ,但是后边又说了,如果读到这个文件的最后一个字节(就是读完文件时),要用 -1 来返回,也就是说要用一个byte之外的数字来返回,代表读完了这个文件,所以此时byte就超范围了,所以此时用int来接收这个read的字节。
(2)此时我们来看运行结果: 如下图,我这read的是一个abc,为啥运行read的是二进制了呢,别忘了,我们现在介绍的是字节流,不是字符流,read的是一个个的字节,所以运行出来的这串数字就是对应文件里一个个字符的ASCII码值。