Java之IO流详解

大多数应用程序都需要实现与设备之间的数据传输,例如键盘可以输入数据,显示器可以显示程序的运行结果等。在Java中,将这种通过不同输入输出设备(键盘,内存,显示器,网络等)之间的数据传输抽象表述为”流”,程序允许通过流的方式与输入输出设备进行数据传输。Java中的”流”都位于java.io包中,称为IO(输入输出)流。

IO流四大家族:

  • 1.InputStream:输入字节流。
  • 2.OutputStream:输出字节流。
  • 3.Reader:输入字符流。
  • 4.Writer:输出字符流。

其中1、2统称字节流,3、4统称字符流。接下来就来详细介绍这四大家族流的用法。

1.字节流

1.1概念

在计算机中,无论是文本、图片、音频、还是视频,所有的文件都是以二进制(字节)形式存在,IO流中针对字节的输入输出提供了一系列的流,统称为字节流。字节流是程序中最常用的流,根据数据的传输方向可将其分为字节输入流和字节输出流。在JDK中,提供了两个抽象类InputStream和OutputStream,它们是字节流的顶级父类,所有的字节输入流都继承自InputStream,所有的字节输出流都继承自OutputStream。

注意:以上所说的所有”输入”、”输出”都是相对于程序而言。数据通过输入流从源设备输入到程序,通过输出流从程序输出到目标设备,从而实现数据的传输。

1.2字节输入流(InputStream)

常用方法:

  • int read();从输入流读取一个8位的字节,把它转换成0~255之间的整数,并返回这一整数。
  • int read(byte[] b);从输入流读取若干字节,把它们保存到参数b指定的字节数组中,返回的整数表示读取字节数。
  • int read(byte[] b,int off,int len);从输入流读取若干字节,把它们保存到参数b指定的字节数组中,off指定字节数组开始保存数据的起始下标,len表示读取的字节数目。
  • void close();关闭此输入流并释放与该流关联的所有系统资源。

前三个read()方法都是用来读数据的,其中,第一个read()方法是从输入流中逐个读入字节,而第二个和第三个read()方法则将若干字节以字节数组的形式一次性读入,从而提高读数据的效率。在进行IO流操作时,当前IO流会占用一定的内存,由于系统资源宝贵,因此,在IO流操作结束后,应该调用close()方法关闭流,从而释放当前IO流所占的系统资源。

Demo:读取文件text.txt中的内容

1
2
3
4
5
6
7
8
9
10
11
12
import java.io.*;
public class Example{
public static void main(String[] args){
FileInputStream in=new FileInputStream("text.txt");
int b;//定义一个int类型的变量,记住每次读取的一个字节。
while((b=in.read())!=-1)
{
System.out.prinln(b);//逐个打印出读取的每一个字节
}
in.close();
}
}

1.3字节输出流(OutputStream)

常用方法:

  • void write(int b);向输出流写入一个字节。
  • void write(byte[] b);把参数b指定的字节数组的所有字节写到输出流。
  • void write(byte[] b,int off,int len);将指定byte数组中从偏移量off开始的len个字节写入输出流。
  • void flush();刷新此输出流并强制写出所有缓冲的输出字节。
  • void close();关闭此输出流并释放与此流相关的所有系统资源。

前三个是重载的write()方法,都是用于向输出流写入字节,其中,第一个方法逐个写入字节,后两个方法是将若干个字节以字节数组的形式一次性写入,从而提高写数据的效率。flush()方法用来将当前输出流缓冲区(通常是字节数组)中的数据强制写入目标设备,此过程称为刷新。close()方法是用来关闭流并释放与当前IO流相关的系统资源。

Demo:将字符串(首先要将字符串转换为字节)写入到文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.io.*;
public class Example{
public static void main(String[] args){
FileOutputStream out=new FileOutputStream(目标文件的路径);
String str="hello world";
byte[] b=str.getBytes();//字符串调用getBytes()方法即可转换成字节数组。
for(int i=0;i<b.length;i++)
{
out.write(b[i]);
}
out.close();
}
}

InputStream和OutputStream这两个类虽然提供了一系列和读写数据有关的方法,但是这两个类都是抽象类,不能被实例化。因此,针对不同的功能,二者提供了不同的子类。

1.4代码示例

下面这个例子通过对文件的复制来讲解InputStream和OutputStream的用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.io.*;
public class Example{
public static void main(String[] args){
String fileName="/Users/codingBoy/Desktop/example1.jpg";//源文件路径
String fileName2="/Users/codingBoy/Desktop/example2.jpg";//目标文件路径
InputStream in=null;
OutputStream out=null;
try {
in = new FileInputStream(fileName);
out = new FileOutputStream(fileName2);
int b;
while ((b = in.read())!= -1) {
out.write(b);
}
}catch (Exception e)
{
throw new RuntimeException(e);
}finally {
try {
if (in != null) in.close();
if (out!=null) out.close();
}catch (Exception e)
{
throw new RuntimeException(e);
}
}
}
}

1.5字节缓冲流

上面的例子实现了对文件的复制,但是一个字节一个字节的读写,需要频繁的操作文件,效率非常低。为了提高效率,需要使用两个带缓冲的字节流,分别是BufferedInputStream和BufferedOutputStream,它们两个同时也属于上文JavaWeb学习笔记之Jdbc二中讲到的装饰流。下面通过增加字节缓冲流来对上述例子进行变动:

Demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.io.*;
public class Example{
public static void main(String[] args){
String fileName="/Users/codingBoy/Desktop/example1.jpg";//源文件路径
String fileName2="/Users/codingBoy/Desktop/example2.jpg";//目标文件路径
InputStream in=null;
OutputStream out=null;
BufferedInputStream bis=null;
BufferedOutputStream bos=null;
try {
in = new FileInputStream(fileName);
bis = new BufferedInputStream(in);//将输入流对象作为参数传递给输入缓冲流
out = new FileOutputStream(fileName2);
bos = new BufferedOutputStream(out);

int len;
while((len=bis.read())!=-1) {
bos.write(len);
}
catch (Exception e)
{
throw new RuntimeException(e);
}finally {
try {
if (in != null) in.close();
if (bis!=null) bis.close();
if (out!=null) out.close();
if (bos!=null) bos.close();
}catch (Exception e)
{
throw new RuntimeException(e);
}
}
}
}

2.字符流

2.1定义

前面我们讲过的InputStream和OutputStream类在读写文件时操作的都是字节,如果希望在程序中操作字符,使用这两个类就不太方便,为此JDK提供了字符流。同字节流一样,字符流也有两个抽象的顶级父类,分别是Reader和Writer。其中Reader是字符输入流,用于从某个源设备读取字符,Writer是字符输出流,用于向某个目标设备写入字符。其API跟字节流的类似。

2.2输入字符流(Reader)

Demo:读取reader.txt中的字符串

1
2
3
4
5
6
7
8
9
10
11
12
import java.io.*;
public class Example{
public static void main(String[] args){
FileReader reader=new FileReader("reader.txt");
int ch;
while((ch=reader.read())!=-1)
{
System.out.println((char)ch);//通过read()方法读取到的是int类型的值,所以需要进行强制转换。
}
reader.close();
}
}

2.3输出字符流(Writer)

Demo:将字符串输出到目标文件中

1
2
3
4
5
6
7
8
9
10
import java.io.*;
public class Example{
public static void main(String[] args){
FileWrite out=new FileWrite(目标文件的路径);
String str="hello world";

out.write(str);
out.close();
}
}

2.4字符缓冲流

字符流同样提供了带缓冲区的包装流,分别是BufferedWriter和BufferedReader,其中BufferedReader用于对字符输入流的包装,BufferedWriter用于对字符输出流的包装。需要注意的是,在BufferedReader中有一个重要的方法readLine(),该方法用于一次读取一行文本。接下来通过一个例子学习如何使用这两个包装流实现文件的复制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.io.*;
public class Example{
public static void main(String[] args){
String fileName="/Users/codingBoy/Desktop/example1.jpg";//源文件路径
String fileName2="/Users/codingBoy/Desktop/example2.jpg";//目标文件路径
Reader reader=null;
BufferedReader bf=null;
Writer writer=null;
BufferedWriter bw=null;
try {
reader = new Reader(fileName);
br=new BufferedReader(reader);
writer = new Writer(fileName2);
bw=new BufferedWriter(writer);
String str;
while ((str = bf.readLine())!= null) {
bw.write(str);
bw.newLine();//写入一个换行符,该方法会根据不同的操作系统生成相应的换行符。
}
}catch (Exception e)
{
throw new RuntimeException(e);
}finally {
try {
if (reader != null) reader.close();
if (br !=null) br.close();
if (writer!=null) writer.close();
if (bw !=null) bw.close();
}catch (Exception e)
{
throw new RuntimeException(e);
}
}
}
}

3.转换流

前面提到IO流可分为字节流和字符流,有时字节流和字符流之间也需要进行转换。在JDK中提供了两个类可以将字节流转换为字符流,它们分别是InputStreamReader和OutputStreamWriter。

转换流也是一种包装流,其中OutputStreamWriter是Writer的子类,它可以将一个字节输出流包装成字符输出流,方便直接写入字符,而InputStreamReader是Reader的子类,它可以将一个字节输入流包装成字符输入流,方便直接读取字符。

Demo:将字节流转换为字符流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.*;
public class Example{
public static void main(String[] args){
String fileName="/Users/codingBoy/Desktop/example1.jpg";//源文件路径
String fileName2="/Users/codingBoy/Desktop/example2.jpg";//目标文件路径
InputStreamReader isr=new InputStreamReader(new FileInputStream(fileName1));
BufferedReader br=new BufferedReader(isr);

OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream(fileName2));
BufferedWriter bw=new BufferedWriter(osw);

String line;
while((line=br.readLine())!=null)
{
bw.write(line);
}

br.close();
bw.close();
}
}

4.其他IO流

4.1ByteArrayInputStream和ByteArrayOutputStream

在前面的学习中,都是将文件直接存储到硬盘,但有时候我们希望将文件临时存储到缓冲区,方便以后读取。为此JDK中提供了一个ByteArrayOutputStream类。该类会在创建对象时就创建一个byte型数组的缓冲区,当向数组中写数据时,该对象会把所有的数据先写入缓冲区,最后一次行写入文件。
Demo:将数据写入缓冲区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.io.*;
public class Example{
public static void main(String[] args){
String fileName1="/Users/codingBoy/Desktop/example1.jpg";//源文件路径
String fileName2="/Users/codingBoy/Desktop/example2.jpg";//目标文件路径
FileInputStream in=new FileInputStream(fileName1);
ByteArrayOutputStream bos=new ByteArrayOutputStream();
FileOutputStream out=new FileOutputStream(fileName2);

int b;
while((b=in.read())!=-1)
{
bos.write(b);//先将数据写入缓冲区,当需要写入目标文件中的时候再调用输出流的write(bos.toByteArray())方法。
}

in.close();
bos.close();
out.write(bos.toByteArray());
out.close();
}

在该例中,定义了一个ByteArrayOutputStream对象,将从fileName1文件中读取的字节全部写入该对象的缓冲区,通过FileOutputStream对象将缓冲区的数据一次性写入fileName2文件。

与ByteArrayOutputStream类似,ByteArrayInputStream是从缓冲区中读取数据,接下来通过一个案例来演示ByteArrayInputStream如何读取缓冲区的数据。

Demo:

1
2
3
4
5
6
7
8
9
10
11
12
13

import java.io.*;
public class Example{
public static void main(String[] args){
byte[] bytes=new byte[]{97,98,99,100};
ByteArrayInputStream bis=new ByteArrayInputStream(bytes);

int b;
while((b=bis.read())!=-1)
{
System.out.println((char)b);
}
}

2018.3.19更

欢迎加入我的Java交流1群:659957958。

2018.4.21更:如果群1已满或者无法加入,请加Java学习交流2群:305335626

5.联系

If you have some questions after you see this article,you can tell your doubts in the comments area or you can find some info by clicking these links.

记得扫一扫领一下红包再走哦