InputStream和OutputStream
InputStream是所有表示位输入流的类之父类,OutputStream是所有表示位输出流的类之父类.它们都是抽象类,继承它们的子类要重新定义其中所定义的抽象方法..
例1可以读取键盘输入流,in对象的read()方法一次读取一个字节的数据,读入的数据以int 类型返回.所以在使用out对象将数据显示出来时,就是十进制方式.
例1 StreamDemo.java
import java.io.*;
public class StreamDemo {
public static void main(String[] args){
try{
System.out.print("输入字符:");
System.out.println("输入字符十进制表示:" + System.in.read());
}
catch(IOException e){
e.printStackTrace();
}
}
}
FileInputStream和FileOutputStream
java.io.FileInputStream是InputStream的子类.
当建立一个FileInputStream或FileOutputStream的实例时,必须指定文件位置及文件名称,实例被建立时文件的流就会开启;而不使用流时,必须关闭文件流,以释放与流相依的系统资源,完成文件读/写的动作.
FileInputStream可以使用read()方法一次读入一个字节,并以int类型返回,或者是使用read()方法时读入至一个byte数组,byte数组的元素有多少个,就读入多少个字节.在将整个文件读取完成或写入完毕的过程中,这么一个byte数组通常被当作缓冲区,因为这么一个byte数组通常扮演承载数据的中间角色.
例2是使用FileInputStream与FileOutputStream的一个例子.程序可以复制文件,它会先从来源文件读取数据至一个byte数组中,然后再将byte数组的数据写入目的文件.
例2 FileStreamDemo.java
import java.io.*;
public class FileStreamDemo {
public static void main(String[] args) {
try {
byte[] buffer = new byte[1024];
// 来源文件
FileInputStream fileInputStream = new FileInputStream(new File(
args[0]));
// 目的文件
FileOutputStream fileOutputStream = new FileOutputStream(new File(
args[1]));
// available()可取得未读取的数据长度
System.out.println("复制文件:" + fileInputStream.available() + "字节");
while (true) {
if (fileInputStream.available() < 1024) {
// 剩余的数据比1024字节少
// 一位一位读出再写入目的文件
int remain = -1;
while ((remain = fileInputStream.read()) != -1) {
fileOutputStream.write(remain);
}
break;
} else {
// 从来源文件读取数据至缓冲区
fileInputStream.read(buffer);
// 将数组数据写入目的文件
fileOutputStream.write(buffer);
}
}
// 关闭流
fileInputStream.close();
fileOutputStream.close();
System.out.println("复制完成");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("using:java FileStreamDemo src des");
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
程序中示范了两个read()方法,一个方法可以读入指定长度的数据至数组,另一个方法一次可以读入一个字节.每次读取之后,读取的光标都会往前进,如果读不到数据刚返回-1.使用available()方法可以获得还有多少字节可以读取.除了使用File来建立FileInputStream,FileOutputStream的实例之外,也可以直接使用字符串指定路径来建立.如
//来源文件
FileInputStream fileInputStream = new FileInputStream(args[0]);
//目的文件
FileOutputStream fileOutputStream = new FileOutputStream(args[1]);
在不使用文件流时,记得使用close()方法自行关闭流,以释放与流相依的系统资源.
FileOutputStream默认会以新建文件的方式来开启流.如果指定的文件名称已经存在,则原文件会被覆盖;如果想以附加的模式来写入文件,则可以在构建FileOutputStream实例时指定为附加模式.例如:
FileOutputStream fileOutputStream = new FileOutputStream(args[1],true);
构建方法的第二个append参数如果设定为true,在开启流时如果文件不存在则会新建一个文件,如果文件存在就直接开启流,并将写入的数据附加至文件末端.
BufferedInputStream 和 BufferedOutputStream
java.io.BufferedInputStream与java.io.BufferedOutputStream可以为InputStream、OutputStream类的对象增加缓冲区功能,构建BufferedInputStream实例时,需要给定一个InputStream类型的实例,实现BufferedInputStream时,实际上最后是实现InputStream实例.同样地,在构建BufferedOutputStream时,也需要给定一个OutputStream实例,实现BufferedOutputStream时,实际上最后是实现OutputStream实例.
BufferedInputStream的数据成员buf是一个位数组,默认为2048字节,BufferedOutputStream的数据成员buf 也是一个位数组,默认为512字节.
例3是对例2的改写,不用自行设定缓冲区,比较简单且有效率.
例3 BufferedStreamDemo.java
import java.io.*;
public class BufferedStreamDemo {
public static void main(String[] args){
try{
byte[] data = new byte[1];
File srcfile = new File(args[0]);
File desfile = new File(args[1]);
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(srcfile));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(desfile));
System.out.println("复制文件:" + srcfile.length() + "字节");
while(bufferedInputStream.read(data) != -1){
bufferedOutputStream.write(data);
}
//将缓冲区中的数据全部写出
bufferedOutputStream.flush();
//关闭流
bufferedInputStream.close();
bufferedOutputStream.close();
System.out.println("复制完成");
}
catch(ArrayIndexOutOfBoundsException e){
System.out.println("using java UseFileStream src des");
e.printStackTrace();
}
catch(IOException e){
e.printStackTrace();
}
}
}
为了确保缓冲区中的数据一定被写出至目的地,建议最后执行flush()将缓冲区中的数据全部写出目的流中,这个范例的执行结果与例2的执行结果是相同的.
DatainputStream 和 DataOutputStream
java.io.DataInputStream 和 java.io.DataOutputStream 可提供一些对Java基本数据类型写入的信息,成员数据的类型假设都是Java的基本数据类型,这样的需求不必要使用到与Object输入、输出的流对象,可以使用DataInputStream 、DataOutputStream来写入或读出数据.
示例:先设计一个Member类.
例4 Member.java
public class Member {
private String name;
private int age;
public Member(){
}
public Member(String name,int age){
this.name = name;
this.age = age;
}
public void setName(String name){
this.name = name;
}
public void setAge(int age){
this.age = age;
}
public String getname(){
return name;
}
public int getAge(){
return age;
}
}
下面打算将Member类实例的成员数据写入文件中,并打算在读入文件数据后,将这些数据还原为Member对象.
例5 DataStreamDemo.java
import java.io.*;
public class DataStreamDemo {
public static void main(String[] args){
Member[] members = {new Member("A", 91),
new Member("B", 80),
new Member("C", 86)};
try{
DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream(args[0]));
for(Member member:members){
//写入UTF字符串
dataOutputStream.writeUTF(member.getname());
//写入int字符串
dataOutputStream.writeInt(member.getAge());
}
//读出所有数据至目的地
dataOutputStream.flush();
//关闭流
dataOutputStream.close();
DataInputStream dataInputStream = new DataInputStream(new FileInputStream(args[0]));
//读出数据并还原为对象
for(int i = 0; i < members.length; i++){
//读出UTF字符串
String name = dataInputStream.readUTF();
//读出int数据
int score = dataInputStream.readInt();
members[i] = new Member(name,score);
}
//关闭流
dataInputStream.close();
//显示还原后的数据
for(Member member:members){
System.out.printf("%s/t%d%n",member.getname(),member.getAge());
}
}
catch(IOException e){
e.printStackTrace();
}
}
}
在从文件中读出数据时,不用费心地自行判断读入字符串时或读入int类型时何时该停止,使用对应的readUTF()或readInt()方法可以正确地读入完整类型数据.同样地,DataInputStream、DataOutputStream并没有改变InputStream或OutputStream的行为.
ObjectInputStream和ObjectOutputStream
在Java程序执行的过程中,很多数据都是以对象的方式存在于内存中,有时会希望直接将内存中的整个对象存储至文件,而不是只存储对象中的某些基本类型成员信息,而在下一次程序运行时,希望可以从文件中读出数据并还原为对象.这时可以使用java.io.ObjectInputStream和java.io.ObjectOutputStream来进行这项工作.
如果要直接存储对象,定义该对象的类必须实现java.io.Serializable接口.不过Serializable接口中并没有规范任何必须实现的方法,所以这里所谓实现的意义,其实像是对对象贴上一个标志,代表该对象是可序列化的(Serializable).
为了说明如何直接存储对象,先实现一个User类.
例6 User.java
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int number;
public User(){
}
public User(String name,int number){
this.name = name;
this.number = number;
}
public void setName(String name){
this.name = name;
}
public void setNumber(int number){
this.number = number;
}
public String getName(){
return name;
}
public int getNumber(){
return number;
}
}
注意到serialVersionUID,它代表了可序列化对象的版本.如果没有提供这个版本信息,则实现Serializable接口的类会自动依类名称、实现的接口、成员等来产生.如果是自动产生的,则下次更改User类,自动产生的serialVersionUID也会跟着变更,从文件读回对象时若两个对象的serialVersionUID不相同,就会丢出java.io.InvalidClassException.如果想要维持版本信息的一致,则要明确声明serialVersionUID.
ObjectInputStream 和 ObjectOutputStream 为 InputStream、OutputStream的实例加上了可以让使用者写入对象与读出对象的功能.在写入对象时,要使用 writeObject()方法,读出对象时则使用readObject()方法,被读出的对象都是以Object类型返回.
例7 ObjectStreamDemo.java
import java.io.*;
import java.util.*;
public class ObjectStreamDemo {
public static void main(String[] args){
User[] users = {new User("A",101),new User("B",102),new User("C",103)};
try{
//写入新文件
writeObjectsToFile(users,args[0]);
//读取文件数据
users = readObjectsFromFile(args[0]);
//显示读回的对象
for(User user:users){
System.out.printf("%s/t%d/n",user.getName(),user.getNumber());
}
System.out.println();
users = new User[2];
users[0] = new User("D", 104);
users[1] = new User("E", 105);
//附加新对象至文件
appendObjectsToFile(users,args[0]);
//读取文件数据
users = readObjectsFromFile(args[0]);
//显示读回的对象
for(User user:users){
System.out.printf("%s/t%d/n", user.getName(),user.getNumber());
}
}
catch(ArrayIndexOutOfBoundsException e){
System.out.println("没有指定文件名");
//e.printStackTrace();
}
catch(FileNotFoundException e){
e.printStackTrace();
}
catch(Exception e){
e.printStackTrace();
System.out.println("没有指定文件名");
}
}
//将指定的对象写入至指定的文件
public static void writeObjectsToFile(Object[] objs,String filename){
File file = new File(filename);
try{
ObjectOutputStream objOoutputStream = new ObjectOutputStream(new FileOutputStream(file));
for(Object obj:objs){
//将对象写入文件
objOoutputStream.writeObject(obj);
}
//关闭流
objOoutputStream.close();
}
catch(IOException e){
e.printStackTrace();
}
}
//将指定文件中的对象数据读回
public static User[] readObjectsFromFile(String filename)
throws FileNotFoundException{
File file = new File(filename);
//如果文件不存在就丢出异常
if (!file.exists())
throw new FileNotFoundException();
//使用List先存储读回的对象
List<User> list = new ArrayList<User>();
try{
FileInputStream fileInputStream = new FileInputStream(file);
ObjectInputStream objInputStream = new ObjectInputStream(fileInputStream);
while (fileInputStream.available() > 0){
list.add((User) objInputStream.readObject());
}
objInputStream.close();
}
catch(ClassNotFoundException e){
e.printStackTrace();
}
catch(IOException e){
e.printStackTrace();
}
User[] users = new User[list.size()];
return list.toArray(users);
}
public static void appendObjectsToFile(Object[] objs, String filename)
throws FileNotFoundException{
File file = new File(filename);
//如果文件不存在则丢出异常
if (!file.exists())
throw new FileNotFoundException();
try{
//附加模式
ObjectOutputStream objOutputStream = new ObjectOutputStream(
new FileOutputStream(file,true)){
//如果要附加对象至文件后
//必须重新定义这个方法
protected void writeStreamHeader() throws IOException{}
};
for (Object obj : objs){
//将对象写入文件
objOutputStream.writeObject(obj);
}
objOutputStream.close();
}
catch (IOException e){
e.printStackTrace();
}
}
}
注意,在试图将对象附加至一个先前已写入对象的文件时,由于ObjectOutputStream在写入数据时,还会加上一个特别的流头(Stream Header),所以在读取文件时会检查这个流头,如果一个文件中被多次附加对象,那么该文件中会有多个流头,这样读取检查时就会发现不一致,这会丢出java.io.StreamCorrupedException.为了解决这个问题,可以重新定义ObjectOutputStream的writeStreamHeader()方法.如果是以附加的方式来写入对象,就不写入流头:
ObjectOutputStream objOutputStream = new ObjectOutputStream(
new FileOutputStream(file,true)) {
protected void writeStreamHeader()
throws IOException{}
};
序列化对象的功能并不只应用于文件的读取或写入,也可以应用于其他领域.例如将对象直接通过网络进行传送或是传送影像的位数组数据等.
SequenceInputStream
若要将一个文件侵害为数个文件,再将之组合还原为一个文件,最基本的作法是使用数个FileInputStream来打开分割后的文件,然后一个一个文件的读取,并使用同一个FileOutputStream实例写到同一个文件中,必须要自行判断每一个分割文件的读取是否完毕,如果完毕就读取下一个文件.
如果使用java.io.SequenceInputStream就不用这么麻烦,SequenceInputStream可以看作是数个InputStream对象的组合.当一个InputStream对象的内容读取完毕后,它就会取出下一个InputStream对象,直到所有的InputStream对象都读取完毕为止.
PrintStream
ByteArrayInputStream 和 ByteArrayOutputSTream
PushbackInputStream
File类
一个File的实例被建立时,它就不能再被改变内容.File实例除了用作一个文件或目录的抽象表示之外,它还提供了不少相关操作方法:可以用它来对文件系统作一些查询与设定的动作.要注意的是,不管是文件还是目录,在Java中都是以File的实例来表示..
以下是一个设定与操作File实例的简单示例,可以指定查询某个目录下的所有文件与目录名称.
FileDemo.java
import java.io.*;
import java.util.*;
public class FileDemo {
public static void main(String[] args){
try{
File file = new File(args[0]);
if (file.isFile()){//是否为文件
System.out.println(args[0] + "文件");
System.out.print(file.canRead()? "可读" :"不可读");
System.out.print(file.canWrite() ? "可写": "不可写");
System.out.println(file.length() + "字节");
}
else{
//列出所有的文件及目录
File[] files = file.listFiles();
ArrayList<File> fileList = new ArrayList<File>();
for (int i = 0; i < files.length; i++){
//先列出目录
if (files[i].isDirectory()) {
//取得路径名
System.out.println("[" + files[i].getPath() + "]");
}
else{
//文件先存入fileList,待会再列出
fileList.add(files[i]);
}
}
//列出文件
for(File f: fileList){
System.out.println(f.toString());
}
System.out.println();
}
}
catch(ArrayIndexOutOfBoundsException e){
System.out.println("using: java FileDemo pathname");
}
}
}
File类主要是文件的抽象代表,若要作文件输出/输入,必须配合其他相关类来使用.