目录
1)在 Java 中,文件的操作分为两类 | |
操作文件系统 | 通过 File 类,在系统中进行增、删、查等操作。 |
操作文件内容 | 通过 数据流对象,在文件中读取或写入内容。(下文介绍该类) |
2)什么是数据流? | |
数据流是一个抽象概念,水流可以流动,而数据也具有跟水流类似的特点,同时两者也都可以被容器容纳。 |
3)与文件的类型分类类似,数据流也有两种分类 | |
字符流 | 文本文件是指保存合法字符的文件,字符以字符串形式保存。以字符形式传输的流对象,被称为字符流。 |
字节流 | 二进制文件是指文件保存的是二进制数据。以字节形式传输的流对象,被称为字节流。 |
Java标准库中提供的用于读写文件的流对象有很多类,但是这些类都可以归纳到上述两个大的种类中。 |
4)流也分为输入流和输出流 | |||
每一个种类的流对象,都会有自己的输入流和输出流,在Java中也使用不同的类来表示。 | |||
字符流 | 输入流类:Reader | ||
输出流类:Writer | |||
字节流 | 输入流类:InputStream | ||
输出流类:OutputStream | |||
下文将介绍上述流对象的使用。 |
1)流为什么需要关闭? | |
这里的流是指文件的数据流,每打开一个文件,就会在内存中建立一个PCB。 PCB通过文件描述符表对这些打开的文件进行描述,不使用文件了却不关闭文件,则文件描述附表会一直被占用。 类似于内存泄漏,上述的情况就造成了文件资源泄露。 文件描述符表有存储上限的,一旦没有关闭的文件超过文件描述符表的上限,就会抛出异常。 虽然 Java 有垃圾回收机制,但该机制适用于内存资源的回收。而此处泄露的是文件资源。 因此,只有在使用完毕后,关闭流,才能释放文件描述符表这样的文件资源,才不会造成文件资源泄露。 |
2)怎么关闭流对象? | |||
流对象主要通过以下三种方式进行关闭: | |||
<1> | 使用 close() 方法 | ||
<2> | 使用 try-finally | ||
<3> | 使用?try-with-resources |
每个数据流的类,通常都会有一个 close() 方法,用于将这个流的对象关闭。 |
使用方式:直接用流对象调用?close() 方法即可。 |
缺点:直接用流对象调用?close() 方法固然可行,但程序如果结构复杂,极可能出现忘记调用,或虽然代码中有调用 close(),却因为代码结构问题或抛出异常问题,而无法执行到这个方法。 |
try-finally 语法的含义是执行 try 代码块中的代码,无论这些代码是正常执行完毕还是抛出异常,最终都必须执行 finally 代码块中的代码。 |
语法演示:
try{
//创建流对象;
//需要执行的代码;
}finally{
流对象.close();
}
Java 还提供了一种更简洁明了的方式,来帮助程序员更好的管理资源。 |
try-with-resources 是指将使用后需要关闭的资源,在 try 关键字后使用 () 进行包裹,这样在程序运行出 try 代码块后,() 中包裹的资源将被自动释放。 |
语法演示:
try( //创建流对象,将需要在代码块运行完成后关闭的资源放在这里 ){
//需要执行的代码;
}
在下文的代码演示中,将统一使用这种关闭文件资源的方式。 |
字符流通过?Reader 类对数据进行读,Reader 是一种输入流。 |
字符流通过 Writer 类对数据进行写,Writer 是一种输出流。 |
Reader 类和 Writer 类都是抽象类,创建实例时需要使用他们的子类。 |
Reader 使用 read() 方法读取数据,read() 方法如果返回 -1 表示读取到文件末尾,read() 方法有以下三种方法重载 |
read() :无参数,一次读取一个字符。 |
read(char[] cbuf) :以数组为参数,最多读取 cbuf.length 字符的数据到数组中,返回实际读取的字符数量。 |
read(char[] cbuf, int off, int len) :以数组为参数,最多读取 cbuf.length 字符的数据到数组中,会从数组的第 off 个元素开始,将 len 长度的字符填入数组中,返回实际读取的字符数量。 |
代码演示使用 read() 方法:
//存在当前文件,路径为"C:/Test/A/test.txt",内容为"加油gogogo";
public static void main(String[] args) throws IOException {
//打开文件C:/Test/A/test.txt;
try(Reader reader = new FileReader("C:/Test/A/test.txt")){
while (true){
//读取一个字符;
int ch = reader.read();
//判断是否到达文件末尾;
if (ch == -1){
break;
}
//打印读取到的字符;
System.out.println((char)ch);
}
}
}
//运行结果:
加
油
g
o
g
o
g
o
代码演示使用?read(char[] cbuf) 方法:
//存在当前文件,路径为"C:/Test/A/test.txt",内容为"加油gogogo";
public static void main(String[] args) throws IOException {
//打开文件C:/Test/A/test.txt;
try(Reader reader = new FileReader("C:/Test/A/test.txt")){
while (true){
//创建一个数组,用来存储读取到的字符;
char[] cbuf = new char[1024];
//将读取到的字符存入cbuf数组中,返回实际读取到的字符个数;
int len = reader.read(cbuf);
//判断是否到达文件末尾;
if (len == -1){
break;
}
//打印cbuf数组中的字符;
for (int i = 0; i < len; i++){
System.out.print(cbuf[i]);
}
}
}
}
//运行结果:
加油gogogo
Writer 使用 write() 方法写入数据,write() 方法有以下五种方法重载 |
write(int c) :每次写入一个字符。 |
write(String str) :每次写入一个字符串; |
write(char[] cbuf) :每次写入多个字符; |
write(String str, int off, int len) :每次写入一个字符串,从字符串中的off位置开始去写,写len长度; |
write(char[] cbuf, int off, int len) :每次写入多个字符,从字符数组中的off位置开始去写,写len长度; |
由于写入的数据可能保存在缓冲区,未来得及写入文件中。因此,为确保我们可以及时看到写入的内容,Writer 类提供了 flush() 方法,用于刷新缓冲区。 |
代码演示使用?write(int c)?方法:
//存在当前文件,路径为"C:/Test/A/test.txt",内容为"加油gogogo";
public static void main(String[] args) throws IOException {
//打开文件C:/Test/A/test.txt;
try(Writer writer = new FileWriter("C:/Test/A/test.txt")){
//在文件中写入对应字符串;
writer.write("我的愿望是,世界和平!");
//刷新缓冲区;
writer.flush();
}
}
//运行结果:
打开文件 C:/Test/A/test.txt ,
发现原来的内容"加油gogogo"被替换为"我的愿望是,世界和平!"
通过上述代码,我们可以发现,原先文件中的文本内容被覆盖了。 |
在上述代码中,每次写入新的文本前,都会将原有的文本先清空再写入。 |
如果不想清空原有文本,而是在原有文本之后继续写入,则需要在打开文件时,在 ?FileWriter 的构造方法的参数中填入另一个 boolean 类型的参数。当参数为 true 时,则表示追加文本。 |
代码演示追加文本:
//存在当前文件,路径为"C:/Test/A/test.txt",内容为"加油gogogo";
public static void main(String[] args) throws IOException {
//打开文件C:/Test/A/test.txt ,参数中添加true;
try(Writer writer = new FileWriter("C:/Test/A/test.txt",true)){
//在文件中写入对应字符串;
writer.write("我的愿望是,世界和平!");
//刷新缓冲区;
writer.flush();
}
}
//运行结果:
打开文件 C:/Test/A/test.txt ,
发现内容为"加油gogogo我的愿望是,世界和平!"
字节流通过?InputStream?类对数据进行读,InputStream?是一种输入流。 |
字节流通过 OutputStream?类对数据进行写,OutputStream?是一种输出流。 |
InputStream?类和 OutputStream?类都是抽象类,创建实例时需要使用他们的子类。 |
InputStream?使用 read() 方法读取数据,read() 方法如果返回 -1 表示读取到文件末尾,read() 方法有以下三种方法重载 |
read() :无参数,一次读取一个字节。 |
read(byte[] b) :以数组为参数,最多读取 b.length 字节的数据到数组中,返回实际读取的字节数量。 |
read(byte[] cbuf, int off, int len) :以数组为参数,最多读取 b.length 字节的数据到数组中,会从数组的第 off 个元素开始,将 len 长度的字节填入数组中,返回实际读取的字节数量。 |
代码演示使用 read() 方法:
//存在当前文件,路径为"C:/Test/A/test.txt",内容为"abc";
public static void main(String[] args) throws IOException {
//打开文件C:/Test/A/test.txt;
try (InputStream is = new FileInputStream("C:/Test/A/test.txt")){
while (true){
//读取一个字节;
int n = is.read();
//判断是否到达文件末尾;
if(n == -1){
break;
}
//打印;
System.out.printf("%c ",n);
System.out.println(n);
}
}
}
//运行结果:
a 97
b 98
c 99
代码演示使用?read(byte[] b) 方法:
//存在当前文件,路径为"C:/Test/A/test.txt",内容为"abc";
public static void main(String[] args) throws IOException {
//打开文件C:/Test/A/test.txt;
try (InputStream is = new FileInputStream("C:/Test/A/test.txt")){
while (true){
//创建一个数组,用来存储读取到的字节;
byte[] b = new byte[1024];
//将读取到的字节存入b数组中,返回实际读取到的字节个数;
int len = is.read(b);
//判断是否到达文件末尾;
if(len == -1){
break;
}
//打印;
for(int i=0;i<len;i++){
System.out.printf("%c ",b[i]);
System.out.println(b[i]);
}
}
}
}
//运行结果:
a 97
b 98
c 99
根据使用的字符集的不同,一个中文字符通常会占用两到三个字节。如果我们需要从文件中读取中文字符会非常麻烦。 |
因此,可以使用 Scanner 类,来帮助我们从字节流结果中获取中文字符。 |
代码演示字节流获取中文字符的方法:
//存在当前文件,路径为"C:/Test/A/test.txt",内容为"我的愿望是,世界和平!";
public static void main(String[] args) throws IOException {
//打开文件C:/Test/A/test.txt;
//将字节流通过 Scanner 类转换为字符流,指定通过“utf8”字符集进行转换;
try (InputStream is = new FileInputStream("C:/Test/A/test.txt");
Scanner sc = new Scanner(is,"utf8")){
//循环判断是否有下一行;
while (sc.hasNext()){
//获得下一行;
String str = sc.next();
//打印;
System.out.println(str);
}
}
}
//运行结果:
我的愿望是,世界和平!
OutputStream?使用 write() 方法写入数据,write() 方法有以下三种方法重载 |
write(int b) :每次写入一个字节。 |
write(char[] cbuf) :每次写入多个字节; |
write(char[] cbuf, int off, int len) :每次写入多个字符,从字符数组中的off位置开始去写,写len长度; |
由于写入的数据可能保存在缓冲区,未来得及写入文件中。因此,为确保我们可以及时看到写入的内容,OutputStream?类提供了 flush() 方法,用于刷新缓冲区。 |
由于字节流传输的是字节数据,但我们仍需要以字符形式输入,因此下文将以两种将字符转换为字节的方法分别进行演示。 |
代码演示使用?write(int b)?方法(以?getBytes() 转换字符的方式):
//存在当前文件,路径为"C:/Test/A/test.txt";
public static void main(String[] args) throws IOException {
//打开文件C:/Test/A/test.txt;
try(OutputStream writer = new FileOutputStream("C:/Test/A/test.txt")){
//需要在文件中写入的字符串;
String str = "我的愿望是,世界和平!";
//将字符转换为字节并写入文件;
writer.write(str.getBytes());
//刷新缓冲区;
writer.flush();
}
}
//运行结果:
打开文件 C:/Test/A/test.txt ,
文件内容为"我的愿望是,世界和平!"
代码演示使用?write(int b)?方法(以?PrintWriter 包裹流对象的方式):
//存在当前文件,路径为"C:/Test/A/test.txt";
public static void main(String[] args) throws IOException {
//打开文件C:/Test/A/test.txt;
try(OutputStream writer = new FileOutputStream("C:/Test/A/test.txt")){
//使用PrintWriter类将输入的字符自动转换为字节;
PrintWriter printWriter = new PrintWriter(writer);
//写入文件;
printWriter.write("我的愿望是,世界和平!");
//刷新缓冲区;
writer.flush();
}
}
//运行结果:
打开文件 C:/Test/A/test.txt ,
文件内容为"我的愿望是,世界和平!"
阅读指针 -> 《?网络编程 -- 网络通信基础(协议和协议分层、数据封装和分用)?》