IO流
文章目录
前言
大多数应用程序都需要实现与设备之间的数据传输,例如键盘可以输入数据,显示器可以显示程序的运行结果等。在Java中,将这种通过不同输入输出设备(键盘、内存、显示器、网络等)之间的数据传输抽象表述为“流”,程序允许通过流的方式与输入输出设备进行数据传输。Java中的“流”都位于java.io包中,称为IO(输入输出)流。IO 流有很多种,按照操作数据的不同,可以分为字节流和字符流,按照数据传输方向的不同又可分为输入流和输出流,程序从输入流中读取数据,向输出流中写入数据。在IO包中,字节流的输入输出流分别用java.io.InputStream和java.io.OutputStream表示,字符流的输入输出流分别用java.io.Reader和java.io. Writer表示,具体分类如图所示。
1.字节流
1.1 字节流的概念
在计算机中,无论是文本、图片、音频还是视频,所有文件都是以二进制(字节)形式存在的,IO 流中针对字节的输入输出提供了一系列的流,统称为字节流。字节流是程序中最常用的流,根据数据的传输方向可将其分为字节输入流和字节输出流。在 JDK 中,提供了两个抽象类InputStream 和OutputStream,它们是字节流的顶级父类,所有的字节输入流都继承自InputStream,所有的字节输出流都继承自Output Stream。为了方便理解,可以把InputStream和OutputStream比作两根“水管”。
InputStream被看成一个输入管道,OutputStream被看成一个输出管道,数据通过InputStream从源设备输入到程序,通过OutputStream从程序输出到目标设备,从而实现数据的传输。由此可见,IO流中的输入输出都是相对于程序而言的。
1.2 FileInputStream的构造方法
FileInputStream(File file):
通过打开与实际文件的连接创建一个FileInputStream,该文件由文件系统中的File对象file命名。
FileInputStream(String string):
通过打开与实际文件的连接来创建一个FileInputStream,该文件由文件系统中的路径名name命名。
1.3 FileInputStream的成员方法
public int read():从该输入流读取一个字节的数据
public int read(byte[] b):从该输入流读取最多b.length个字节的数据为字节数组
代码举例:read()第一种方式,一次读取一个字节
public class Demo1 {
public static void main(String[] args) throws Exception{
// File file = new File("f.txt");
// FileInputStream fis = new FileInputStream(file);
// 当封装一个不存在的文件,报错:系统找不到指定的文件。
FileInputStream fis = new FileInputStream("d.txt");
int i=0;
// 一次读取一个字节,返回的是ASCII码值
while((i= fis.read())!=-1){
System.out.print((char)i);//强制类型转换
}
fis.close();
}
}
输出
A0baBaaaA0
read(byte[] b) 第二种方式,一次读取一个字节数组
import java.io.FileInputStream;
public class FileInputStreamDemo1 {
public static void main(String[] args) throws IOException {
//FileInputStream(File file)
//将要读取的目标文件封装成File对象。以参数的形式传入构造方法中
//对于字节输入流读取文件而言,目标文件必须存在,否则会出现异常
// File file = new File("java/src/com/shujia/wyh/day12/b.txt");
// FileInputStream fis = new FileInputStream(file);
//FileInputStream(String name)
FileInputStream fis = new FileInputStream("java/src/com/shujia/wyh/day12/b.txt");
//将来我们不清楚一个文件有多少个字节所以使用while循环读取
//通过观察api发现,如果读取到了数据的末尾,返回-1
byte[] bytes = new byte[1024];
//使用while循环改进
int length = 0;
while ((length = fis.read(bytes)) != -1) {
String s = new String(bytes, 0, length);
System.out.println(s);
}
//释放资源
fis.close();
}
}
输出
qwerdf
创建的字节流FileInputStream通过read()方法将当前项目中的文件中的数据读取并打印。运行结果可以看出,结果分别为Ascll码值。通常情况下读取文件应该输出字符,这里之所以输出数字是因为硬盘上的文件是以字节的形式存在的。在文件中,字符qwerdf各占一个字节,因此,最终结果显示的就是文件“test.txt”中的6个字节所对应的十进制数,所以我们要转换一下。
1.4FileOutputStream
与FileInputStream对应的是FileOutputStream。FileOutputStream是OutputStream的子类,它是操作文件的字节输出流,专门用于把数据写入文件。接下来通过一个案例来演示如何将数据写入文件。
示例
1 import java.io.*;
2 public class Example02 {
3 public static void main(String[] args) throws Exception {
4 // 创建一个文件字节输出流
5 FileOutputStream out = new FileOutputStream("example.txt");
6 String str = "传智播客";
7 byte[] b = str.getBytes();
8 for (int i = 0; i < b.length; i++) {
9 out.write(b[i]);
10 }
11 out.close();
12 }
13 }
传播智客
从运行结果可以看出,通过FileOutputStream写数据时,自动创建了文件example.txt,并将数据写入文件。需要注意的是,如果是通过 FileOutputStream 向一个已经存在的文件中写入数据,那么该文件中的数据首先会被清空,再写入新的数据。若希望在已存在的文件内容之后追加新内容,则可使FileOutputStream的构造函数FileOutputStream(String fileName, boolean append)来创建文件输出流对象,并把append 参数的值设置为true。
1.5 FileOutputStream(String fileName, boolean append)
1 import java.io.*;
2 public class Example03 {
3 public static void main(String[] args) throws Exception {
4 OutputStream out = new FileOutputStream("example.txt ", true);
5 String str = "欢迎你!";
6 byte[] b = str.getBytes();
7 for (int i = 0; i < b.length; i++) {
8 out.write(b[i]);
9 }
10 out.close();
11 }
12 }
传播智客欢迎你
可以看出,程序通过字节输出流对象向文件“example.txt”写入“欢迎你!”后,并没有将文件之前的数据清空,而是将新写入的数据追加到了文件的末尾。
由于 IO 流在进行数据读写操作时会出现异常,为了代码的简洁,在上面的程序中使用了throws关键字将异常抛出。然而一旦遇到IO异常,IO流的close()方法将无法得到执行,流对象所占用的系统资源将得不到释放,因此,为了保证IO流的close()方法必须执行,通常将关闭流的操作写在finally代码块中,具体代码如下所示。
finally{try{if(in!=null)
// 如果in不为空,关闭输入流in.close();}catch(Exception e){e.printStackTrace();}try{if(out!=null)
// 如果out不为空,关闭输出流
out.close();}
catch(Exception e){e.printStackTrace();
}
}
2.文件拷贝
2.1 概述
在应用程序中,IO 流通常都是成对出现的,即输入流和输出流一起使用。例如文件的拷贝就需要通过输入流来读取文件中的数据,通过输出流将数据写入文件。
2.2 文件内容的拷贝。
首先在当前项目目录下创建文件夹 source 和 target(右键单击项目名称→【New】→【Folder】),然后在 source 文件夹中存放一个“五环之歌.mp3”文件,拷贝文件的代码如文件7-4所示。
1 import java.io.*;
2 public class Example04 {
3 public static void main(String[] args) throws Exception {
4 // 创建一个字节输入流,用于读取当前目录下source文件夹中的mp3文件
5 InputStream in = new FileInputStream("source\\五环之歌.mp3");
6 // 创建一个文件字节输出流,用于将读取的数据写入target目录下的文件中
7 OutputStream out = new FileOutputStream("target\\五环之歌.mp3");
8 int len; // 定义一个int类型的变量len,记住每次读取的一个字节
9 long begintime =System.currentTimeMillis(); // 获取拷贝文件前的系统时间
10 while ((len = in.read()) != -1) { // 读取一个字节并判断是否读到文件末尾
11 out.write(len); // 将读到的字节写入文件1
2 }
13 long endtime = System.currentTimeMillis();// 获取文件拷贝结束时的时间
14 System.out.println("拷贝文件所消耗的时间是:" + (endtime - begintime) +
15 "毫秒");1
6 in.close();
17 out.close();
18 }
19 }
程序运行结束后,刷新并打开target文件夹,发现source文件夹中的“五环之歌.mp3”文件被成功拷贝到了target文件夹,如图7-9所示。
文件7-4中实现了mp3文件的拷贝。在拷贝过程中,通过while循环将字节逐个进行拷贝。每循环一次,就通过FileInputStream 的read()方法读取一个字节,并通过FileOutputStream的write()方法将该字节写入指定文件,循环往复,直到len的值为-1,表示读取到了文件的末尾,结束循环,完成文件的拷贝。程序运行结束后,会在命令行窗口打印拷贝mp3文件所消耗的时间,如图7-10所示。
图7-10 运行结果从图7-10可以看出,程序拷贝mp3文件共消耗了12074毫秒。在拷贝文件时,由于计算机性能等各方面原因,会导致拷贝文件所消耗的时间不确定,因此每次运行程序结果未必相同。需要注意的是,文件7-4中在定义文件路径时使用了“\”。这是因为在Windows中的目录符号为反斜线“\”,但反斜线“\”在Java中是特殊字符,表示转义符,所以在使用反斜线“\”时,前面应该再添加一个反斜线,即为“\”。除此之外,目录符号也可以用正斜线“/”来表示,如“source/五环之歌.mp3”。
3. 字节流的缓冲区
3.1 概述
虽然7.1.3小节实现了文件的拷贝,但是一个字节一个字节地读写,需要频繁地操作文件,效率非常低。这就好比从北京运送烤鸭到上海,如果有一万只烤鸭,每次运送一只,就必须运输一万次,这样的效率显然非常低。为了减少运输次数,可以先把一批烤鸭装在车厢中,这样就可以成批地运送烤鸭,这时的车厢就相当于一个临时缓冲区。当通过流的方式拷贝文件时,为了提高效率也可以定义一个字节数组作为缓冲区。在拷贝文件时,可以一次性读取多个字节的数据,并保存在字节数组中,然后将字节数组中的数据一次性写入文件。
3.2 示例
1 import java.io.*;
2 public class Example05 {
3 public static void main(String[] args) throws Exception {
4 // 创建一个字节输入流,用于读取当前目录下source文件夹中的mp3文件
5 InputStream in = new FileInputStream("source\\五环之歌.mp3");
6 // 创建一个文件字节输出流,用于将读取的数据写入当前目录的target文件中
7 OutputStream out = new FileOutputStream("target\\五环之歌.mp3");
8 // 以下是用缓冲区读写文件
9 byte[] buff = new byte[1024]; // 定义一个字节数组,作为缓冲区
10 // 定义一个int类型的变量len记住读取读入缓冲区的字节数
11 int len;
12 long begintime =System.currentTimeMillis();
13 while ((len = in.read(buff)) != -1) { // 判断是否读到文件末尾
14 out.write(buff, 0, len); // 从第一个字节开始,向文件写入len个字节
15 }
16 long endtime =System.currentTimeMillis();
17 System.out.println("拷贝文件所消耗的时间是:" + (endtime - begintime) +
18 "毫秒");
19 in.close();
20 in.close();
21 }
22 }
件7-5同样实现了mp3文件的拷贝。在拷贝过程中,使用while循环语句逐渐实现字节文件的拷贝,每循环一次,就从文件读取若干字节填充字节数组,并通过变量len记住读入数组的字节数,然后从数组的第一个字节开始,将len个字节依次写入文件。循环往复,当len值为-1时,说明已经读到了文件的末尾,循环会结束,整个拷贝过程也就结束了。最终程序会将整个文件拷贝到目标文件夹,并将拷贝过程所消耗的时间打印了出来,如图所示。
通过比较l两次所需时间,可以看出拷贝文件所消耗的时间明显减少了,这说明使用缓冲区读写文件可以有效地提高程序的效率。程序中的缓冲区就是一块内存,该内存主要用于存放暂时输入输出的数据,由于使用缓冲区减少了对文件的操作次数,所以可以提高读写数据的效率。
4. 字节缓冲流
4.1概述
在IO包中提供两个带缓冲的字节流,分别是BufferedInputStream和BufferedOutputStream,它们的构造方法中分别接收InputStream和OutputStream类型的参数作为对象,在读写数据时提供缓冲功能。应用程序、缓冲流和底层字节流之间的关系如图7-12所示。
图· 7-12
从图7-12中可以看出,应用程序是通过缓冲流来完成数据读写的,而缓冲流又是通过底层的字节流与设备进行关联的。
4.2 示例
首先需要在Eclipse的项目目录下创建一个名称为src.txt的文件,并在该文件中写入内容;然后创建一个使用字节缓冲流拷贝该文件的类
1 import java.io.*;
2 public class Example06 {
3 public static void main(String[] args) throws Exception {
4 // 创建一个带缓冲区的输入流
5 BufferedInputStream bis=new BufferedInputStream(new FileInputStream(
6 "src.txt"));
7 // 创建一个带缓冲区的输出流
8 BufferedOutputStream bos = new BufferedOutputStream(
9 new FileOutputStream("des.txt"));
10 int len;
11 while ((len = bis.read()) != -1) {
12 bos.write(len);
13 }
14 bis.close();1
5 bos.close();
16 }
17 }
文件中,创建了BufferedInputStream和BufferedOutputStream两个缓冲流对象,这两个流内部都定义了一个大小为8192的字节数组。当调用read()或者write()方法读写数据时,首先将读写的数据存入定义好的字节数组,然后将字节数组的数据一次性读写到文件中。这种方式与之前讲解的字节流的缓冲区类似,都对数据进行了缓冲,从而有效地提高了数据的读写效率。
5. 字符流
5.1 字符流定义及基本用法
前面已经讲解过InputStream类和OutputStream类在读写文件时操作的都是字节,如果希望在程序中操作字符,使用这两个类就不太方便,为此 JDK 提供了字符流。同字节流一样,字符流也有两个抽象的顶级父类,分别是Reader和Writer。其中,Reader是字符输入流,用于从某个源设备读取字符。Writer 是字符输出流,用于向某个目标设备写入字符。Reader 和 Writer作为字符流的顶级父类,也有许多子类,接下来通过继承关系图来列出Reader和Writer的一些常用子类,如图7-15和图7-16所示。从图7-15和图7-16可以看到,字符流的继承关系与字节流的继承关系有些类似,很多子类都是成对(输入流和输出流)出现的。其中,FileReader 和 FileWriter 用于读写文件,BufferedReader和BufferedWriter是具有缓冲功能的流,使用它们可以提高读写效率。
5.2 字符流操作文件
在程序开发中,经常需要对文本文件的内容进行读取,如果想从文件中直接读取字符,便可以使用字符输入流FileReader,通过此流可以从关联的文件中读取一个或一组字符。接下来通过一个案例来学习如何使用FileReader读取文件中的字符。
首先在项目当前目录下新建文本文件“reader.txt”并在其中输入字符“itcast”,然后创建一个使用字符输入流FileReader读取文件中字符的类
1 import java.io.*;
2 public class Example07 {
3 public static void main(String[] args) throws Exception {
4 // 创建一个FileReader对象用来读取文件中的字符
5 FileReader reader = new FileReader("reader.txt");
6 int ch; // 定义一个变量用于记录读取的字符
7 while ((ch = reader.read()) != -1) { // 循环判断是否读取到文件的末尾
8 System.out.println((char) ch); // 不是字符流末尾就转为字符打印
9 }
10 reader.close(); // 关闭文件读取流,释放资源
11 }
12 }
i
t
c
a
s
t
文件实现了读取文件字符的功能。首先创建一个FileReader对象与文件关联,然后通过while循环每次从文件中读取一个字符并打印,这样便实现了FileReader读文件字符的操作。需要注意的是,字符输入流的read()方法返回的是int类型的值,如果想获得字符就需要进行强制类型转换,如文件7-10中第8行代码的作用就是将变量ch转为char类型再打印。文件7-10讲解了如何使用FileReader读取文件中的字符,如果要向文件中写入字符就需要使用File Writer类,该类是Writer的一个子类。接下来通过一个案例来学习如何使用FileWriter将字符写入文件,
如文件7-11所示。
1 import java.io.*;
2 public class Example08 {
3 public static void main(String[] args) throws Exception {
4 // 创建一个FileWriter对象用于向文件中写入数据
5 FileWriter writer = new FileWriter("writer.txt");
6 String str = "你好,传智播客";
7 writer.write(str); // 将字符数据写入到文本文件中
8 writer.write("\r\n"); // 将输出语句换行
9 writer.close(); // 关闭写入流,释放资源
10 }
11 }
你好,传播智客
ileWriter同FileOutputStream一样,如果指定的文件不存在,就会先创建文件,再写入数据,如果文件存在,则会首先清空文件中的内容,再进行写入。如果想在文件末尾追加数据,同样需要调用重载的构造方法,现将文件7-11中的第5行代码进行如下修改。FileWriter writer = new FileWriter(“writer.txt”,true);修改后,再次运行程序,即可实现在文件中追加内容的效果。通过7.1.5小节的学习,读者已经了解到包装流可以通过对一个已存在的流进行包装来实现数据读写功能,利用包装流可以有效地提高读写数据的效率。字符流同样提供了带缓冲区的包装流,分别是BufferedReader和BufferedWriter。其中,BufferedReader用于对字符输入流进行包装,BufferedWriter 用于对字符输出流进行包装。需要注意的是,在BufferedReader 中有一个重要的方法readLine(),该方法用于一次读取一行文本。
5.3 何使用这两个包装流实现文件的拷贝
示例
1 import java.io.*;
2 public class Example09 {
3 public static void main(String[] args) throws Exception {
4 FileReader reader = new FileReader("src.txt");
5 // 创建一个BufferedReader缓冲对象
6 BufferedReader br = new BufferedReader(reader);
7 FileWriter writer = new FileWriter("des.txt");
8 // 创建一个BufferdWriter缓冲区对象
9 BufferedWriter bw = new BufferedWriter(writer);
10 String str;
11 while ((str = br.readLine()) != null) {
12 bw.write(str);
13 bw.newLine();
14 }
15 br.close();
16 bw.close();
17 }
18 }
itcast传播智客。。。
在文件7-12中,使用了输入输出流缓冲区对象,并通过一个while循环实现了文本文件的拷贝。在拷贝过程中,每次循环都使用readLine()方法读取文件的一行,然后通过write()方法写入目标文件。其中,readLine()方法会逐个读取字符,当读到回车符’\r’或换行符’\n’时会将读到的字符作为一行的内容返回。需要注意的是,由于字符缓冲流内部使用了缓冲区,在循环中调用BufferedWriter的write()方法写入字符时,这些字符首先会被写入缓冲区,当缓冲区写满时或调用 close()方法时,缓冲区中的字符才会被写入目标文件。因此在循环结束时一定要调用 close()方法,否则极有可能会导致部分存在缓冲区中的数据没有被写入目标文件。