Java 字节流以字节为传输单位,用来读写 8 位的数据,除了能够处理纯文本文件之外,还能用来处理二进制文件的数据。InputStream 类和 OutputStream 类是所有字节流的父类。
面向字节的输入流都是 InputStream 类的子类,其类层次结构如图1所示。
表1列出了 InputStream 的主要子类及说明。
类名 | 功能描述 |
---|---|
FileInputStream | 从文件中读取的输入流 |
PipedInputStream | 输入管道 |
FilterInputStream | 过滤输入流 |
ByteArrayInputStream | 从字节数组读取的输入流 |
SequenceInputStream | 两个或多个输入流的联合输入流,按顺序读取 |
ObjectInputStream | 对象的输入流 |
LineNumberInputStream | 为文本文件输入流附加行号 |
DataInputStream | 包含读取 Java 标准数据类型方法的输入流 |
BufferedInputStream | 缓冲输入流 |
PushbackInputStream | 返回一个字节并把此字节放回输入流 |
InputStream 流类中包含一套所有输入都需要的方法,可以完成最基本的从输入流读入数据的功能。表2列出了其中常用的方法及说明。
方法 | 功能描述 |
---|---|
void close() | 关闭输入流 |
void mark() | 标记输入流的当前位置 |
void reset() | 将读取位置返回到标记处 |
int read() | 从输入流中当前位置读入一个字节的二进制数据,以此数据为低位字节,补足16位的整型量(0~255)后返回,若输入流中当前位置没有数据,则返回-1 |
int read(byte b[]) | 从输入流中的当前位置连续读入多个字节保存在数组中,并返回所读取的字节数 |
int read(byte b[], int off, int len) | 从输入流中当前位置连续读len长的字节,从数组第off+1个元素位置处开始存放,并返回所读取的字节数 |
int available() | 返回输入流中可以读取的字节数 |
long skip(long n) | 略过n个字节 |
long skip(long n) | 跳过流内的n个字符 |
boolean markSupported() | 测试输入数据流是否支持标记 |
面向字节的输出流都是OutputStream类的子类,其类层次结构如图2所示。
表3列出了OutputStream的主要子类及说明。
类名 | 功能描述 |
---|---|
FileOutputStream | 写入文件的输出流 |
PipedOutputStream | 输出管道 |
FilterOutputStream | 过滤输出流 |
ByteArrayOutputStream | 写入字节数组的输出流 |
ObjectOutputStream | 对象的输出流 |
DataOutputStream | 包含写Java标准数据类型方法的输出流 |
BufferedOutputStream | 缓冲输出流 |
PrintStream | 包含print()和println()的输出流 |
OutputStream 流类中包含一套所有输出都需要的方法,可以完成最基本的向输出流写入数据的功能。表4列出了其中常用的方法及说明。
方法 | 功能描述 |
---|---|
void close() | 关闭输出流 |
void flush() | 强制清空缓冲区并执行向外设输出数据 |
void write(int b) | 将参数b的低位字节写入到输出流 |
void write(byte b[]) | 按顺序将数组b[]中的全部字节写入到输出流 |
void write(byte b[], int off, int len) | 按顺序将数组b[]中第off+1个元素开始的len个数据写入到输出流 |
由于 InputStream 和 OutputStream 都是抽象类,所以在程序中创建的输入流对象一般是它们某个子类的对象,通过调用对象继承的 read() 和 write() 方法就可实现对相应外设的输入输出操作。
文件输入输出流 FileInputStream 和 FileOutputStream 负责完成对本地磁盘文件的顺序输入输出操作。
【示例1】通过程序创建一个文件,从键盘输入字符,当遇到字符#时结束,在屏幕上显示该文件的所有内容。
- import java.io.*;
- class Demo{
- public static void main(String args[]){
- char ch;
- int data;
- try{
- FileInputStream a=new FileInputStream(FileDescriptor.in); //创建文件输入流对象
- FileOutputStream b=new FileOutputStream("www.cdsy.xyz.txt"); //创建文件输出流对象
- System.out.println("请输入字符,以#号结束:");
- while((ch=(char)a.read())!='#'){
- b.write(ch);
- }
- a.close();
- b.close();
- System.out.println();
- FileInputStream c=new FileInputStream("www.cdsy.xyz.txt");
- FileOutputStream d=new FileOutputStream(FileDescriptor.out);
- while(c.available()>0){
- data=c.read();
- d.write(data);
- }
- c.close();
- d.close();
- }
- catch(FileNotFoundException e){
- System.out.println("找不到该文件!");
- }
- catch(IOException e){}
- }
- }
运行后在程序目录建立一个名称为 www_wcdsy_xyz.txt 的文件,运行结果为:
请输入字符,以#号结束:
www.cdsy.xyz is a great website.#
www.cdsy.xyz is a great website.
FileDescriptor 是 java.io 中的一个类,该类不能实例化,其中包含三个静态成员:in、out 和 err,分别对应于标准输入流、标准输出流和标准错误流,利用它们可以在标准输入输出流上建立文件输入输出流,实现键盘输入或屏幕输出操作。
【示例2】实现对二进制图形文件(.gif)的备份。
- import java.io.*;
- class Demo{
- public static void main(String args[]) throws IOException{
- FileInputStream a=new FileInputStream("www.cdsy.xyz_logo.gif");
- FileOutputStream b=new FileOutputStream("www.cdsy.xyz_logo_back.gif");
- System.out.println("文件的大小为:"+a.available());
- byte c[]=new byte[a.available()];
- a.read(c); //将图形文件读入数组
- b.write(c); //将数组中的数据写入到新文件中
- System.out.println("文件已经被更名复制!");
- a.close();
- b.close();
- }
- }
运行后在程序目录备份了一个名称为 www.cdsy.xyz_log_back.gif 的文件,运行结果:
文件的大小为:15951
文件已经被更名赋值!
FilterInputStream 和 FileOutputStream 是 InputStream 和 OutputStream 的直接子类,分别实现了在数据的读、写操作的同时能对所传输的数据做指定类型或格式的转换,即可实现对二进制字节数据的理解和编码转换。
常用的两个过滤流是数据输入流 DataInputStream 和数据输出流 DataOutputStream。其构造方法为:
DataInputStream(InputStream in); //创建新输入流,从指定的输入流 in 读数据
DataOutputStream(OutputStream out); //创建新输出流,向指定的输出流 out 写数据
由于 DataInputStream 和 DataOutputStream 分别实现了 DataInput 和 DataOutput 两个接口(这两个接口规定了基本类型数据的输入输出方法)中定义的独立于具体机器的带格式的读写操作,从而实现了对不同类型数据的读写。由构造方法可以看出,输入输出流分别作为数据输入输出流的构造方法参数,即作为过滤流必须与相应的数据流相连。
DataInputStream 和 DataOutputStream 类提供了很多个针对不同类型数据的读写方法,具体内容读者可参看 Java 的帮助文档。
【示例3】将三个 int 型数字 100、0、-100 写入数据文件 www.cdsy.xyz.dat 中。
- import java.io.*;
- class Demo{
- public static void main(String args[]){
- String fileName="www.cdsy.xyz.dat";
- int value1=100, value2=0, value3=-100;
- try{
- //将 DataOutputStream 与 FileOutputStream 连接输出不同类型的数据
- DataOutputStream a=new DataOutputStream(new FileOutputStream(fileName));
- a.writeInt(value1);
- a.writeInt(value2);
- a.writeInt(value3);
- a.close();
- }
- catch(IOException i){
- System.out.println("出现错误!"+fileName);
- }
- }
- }
运行后在程序目录中生成数据文件 www.cdsy.xyz.dat,用文本编辑器打开后发现内容为二进制的:
0000 0064 0000 0000 ffff ff9c
【示例4】读取数据文件 www.cdsy.xyz.dat 中的三个 int 型数字,求和并显示。
- import java.io.*;
- class Demo{
- public static void main(String args[]){
- String fileName="www.cdsy.xyz.dat";
- int sum=0;
- try{
- DataInputStream a=new DataInputStream(new BufferedInputStream(new FileInputStream(fileName)));
- sum+=a.readInt();
- sum+=a.readInt();
- sum+=a.readInt();
- System.out.println("三个数的和为:"+sum);
- a.close();
- }
- catch(IOException e){
- System.out.println("出现错误!"+fileName);
- }
- }
- }
运行结果:
三个数的和为:0
readInt 方法可以从输入输出流中读入 4 个字节并将其作为 int 型数据直接参与运算。由于已经知道文件中有 3 个数据,所以可以使用 3 个读入语句,但若只知道文件中是 int 型数据而不知道数据的个数时该怎么办呢?因为 DataInputStream 的读入操作如遇到文件结尾就会抛出 EOFException 异常,所以可将读操作放入 try 中。
- try{
- while(true)
- sum+=a.readInt();
- }
- catch(EOFException e){
- System.out.pritnln("三个数的和为:"+sum);
- a.close();
- }
EOFException 是 IOException 的子类,只有文件结束异常时才会被捕捉到,但如果没有读到文件结尾,在读取过程中出现异常就属于 IOException。
【示例5】从键盘输入一个整数,求该数的各位数字之和。
- import java.io.*;
- class Demo{
- public static void main(String args[]) throws IOException{
- DataInputStream a=new DataInputStream(System.in);
- System.out.print("请输入一个整数:");
- int b=a.readInt();
- int sum=0;
- int c=b;
- while(c>0){
- int d=c%10; //取最低位
- c=c/10; //去掉最低位
- sum=sum+d; //累加各位之和
- }
- System.out.println(b+"的各位数字之和="+sum);
- }
- }
运行结果:
请输入一个整数:26
842403082 的各位数字之和=31
需要注意的是,输入的数据 26 为变成了 842403082,原因在于输入数据不符合基本类型数据的格式,从键盘提供的数据是字符的字节码表示方式,若输入 26,只代表 2 和 6 两个字符的字节数据,而不是代表整数 26 的字节码。
若要从键盘得到整数需要先读取字符串,再利用其他方法将字符串转化为整数。
System.in、System.out、System.err 这 3 个标准输入输流对象定义在 java.lang.System 包中,这 3 个对象在 Java 源程序编译时会被自动加载。
【示例6】输入一串字符显示出来,并显示 System.in 和 System.out 所属的类。
- import java.io.*;
- class Demo{
- public static void main(String args[]){
- try{
- byte a[]=new byte[128]; //设置输入缓冲区
- System.out.print("请输入字符串:");
- int count =System.in.read(a); //读取标准输入输出流
- System.out.println("输入的是:");
- for(int i=0;i<count;i++)
- System.out.print(a[i]+""); //输出数组元素的 ASCII 值
- System.out.println();
- for(int i=0;i<count-2;i++) //不显示回车和换行符
- System.out.print((char)a[i]+""); //按字符方式输出元素
- System.out.println();
- System.out.println("输入的字符个数为:"+count);
- Class InClass=System.in.getClass();
- Class OutClass=System.out.getClass();
- System.out.println("in 所在的类为:"+InClass.toString());
- System.out.println("out 所在的类为:"+OutClass.toString());
- }
- catch(IOException e){}
- }
- }
运行结果:
请输入字符串:abc
输入的是:
9798991310
abc
输入的字符个数为:5
in所在的类为:class java.io.BufferedInputStream
out所在的类为:class java.io.PrintStream
需要注意的是,输入了 3 个字符按回车后,输出的结果显示为 5 个字符。这是由于 Java 中回车被当作两个字符,一个是 ASCII 为 13 的回车符,一个是值为 10 的换行符。程序中 getClass() 和 ToString() 是 Object 类的方法,作用分别是返回当前对象所对应的类和返回当前对象的字符串表示。