面向字节流时,最常用的是文件字节流:FileInputStream / FileOutputStream,按顺序读写本地磁盘文件。再往上套过滤流,就能按基本类型写二进制;最外层则是 System.in / System.out / System.err 三套标准流。
下面几个例子都在教材编号 10-5 到 10-10 的范围内,从建文件、拷贝 gif,到 Data 流读写 int,再到键盘字节。
一、文件输入输出流
【例 10-5】程序创建一个文件,从键盘读字符,遇到 # 结束,再把文件内容打到屏幕上:
import java.io.*;
class ep10_5{
public static void main(String args[]){
char ch;
int data;
try{
FileInputStream a=new FileInputStream(FileDescriptor.in); //创建文件输入流对象
FileOutputStream b=new FileOutputStream("ep10_5"); //创建文件输出流对象
System.out.println("请输入字符,以#号结束:");
while((ch=(char)a.read())!='#'){
b.write(ch);
}
a.close();
b.close();
System.out.println();
FileInputStream c=new FileInputStream("ep10_5");
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){}
}
}
FileDescriptor 在 java.io 中,不能实例化,提供三个静态成员:in、out、err,对应标准输入、标准输出、标准错误。用它们可以在标准流上再包一层文件流,实现键盘输入或屏幕输出。
【例 10-6】备份二进制图形文件(.gif):一次 available() 得到长度,读进 byte 数组再写出。
import java.io.*;
class ep10_6{
public static void main(String args[]) throws IOException{
FileInputStream a=new FileInputStream("ep10_6.gif");
FileOutputStream b=new FileOutputStream("ep10_6_a.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();
}
}
二、过滤流:DataInputStream / DataOutputStream
FilterInputStream 和 FilterOutputStream 是字节流的直接子类,读写的同时可做类型或格式转换,也就是理解二进制、做编码转换。(原文写成 FileOutputStream 作为过滤流父类,应为 FilterOutputStream。)
常用的两个过滤流是 DataInputStream 和 DataOutputStream:
-
DataInputStream(InputStream in):从指定输入流读数据。
-
DataOutputStream(OutputStream out):向指定输出流写数据。
它们分别实现 DataInput / DataOutput,按与机器无关的格式读写基本类型。构造时必须把另一条字节流传进去,过滤流不能单独存在。
【例 10-7】把三个 int:100、0、-100 写入 ep10_7.dat:
import java.io.*;
class ep10_7{
public static void main(String args[]){
String fileName="ep10_7.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);
}
}
}
用文本编辑器打开会看到二进制:00 00 00 64 00 00 00 00 FF FF FF 9C。
【例 10-8】读回这三个 int 并求和(100 + 0 + (−100) = 0):
import java.io.*;
class ep10_8{
public static void main(String args[]){
String fileName="D:\\myjava/ep10_7.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、不知道个数,读到文件尾会抛 EOFException,可把读循环放进 try:
import java.io.*;
class ep10_9{
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
EOFException 是 IOException 的子类:只有碰到文件结束才会进这个 catch;读到一半出错仍是普通 IOException。
【例 10-9】从键盘读一个整数,求各位数字之和:
try{
while(true)
sum+=a.readInt();
}
catch(EOFException e){
System.out.pritnln("三个数的和为:"+sum);
a.close();
}
注意:输入 26,程序却可能打出 842403082 这类大数。因为 DataInputStream.readInt() 要的是 int 的四字节二进制,键盘送来的是字符 ‘2’、’6′ 的字节,并不是整数 26 的补码。要从键盘拿整数,应先读字符串再转换。
| 流 |
职责 |
注意 |
| FileInput/OutputStream |
顺序读写本地文件 |
例 10-5 / 10-6 |
| DataInput/OutputStream |
按基本类型读写二进制 |
必须包在另一条字节流上 |
| System.in / out / err |
标准输入、输出、错误 |
编译时自动加载 |
三、标准输入输出
System.in、System.out、System.err 定义在 java.lang.System,源程序编译时自动加载。
-
标准输入
System.in 是 BufferedInputStream:read() 从键盘缓冲区读一字节,返回低位为该字节、高位为 0 的 int。
-
标准输出
System.out 是 PrintStream(FilterOutputStream 子类),提供 print / println。
-
标准错误
System.err 也是 PrintStream,专门把错误打到屏幕上,方便调试。
【例 10-10】输入一串字符并显示,同时打印 in、out 所属的类:
import java.io.*;
class ep10_10{
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){}
}
}
输入 3 个字符再回车,计数常常是 5:Java 把回车当成两个字符(ASCII 13 的 CR 和 10 的 LF)。getClass() / toString() 来自 Object,分别返回运行时类和字符串表示。
一句话总结:字节流先落地文件,再用 Data 流按类型读写;键盘上的字符不是 int 的四字节,别拿 readInt 直接砸 System.in。