Java类库中的IO类,可以从不同的角度将其进行不同的分类。比如按数据的流向可分分为输入流和输出流;按数据处理单位的不同,又可将其分为字节流和字符流。但不管怎样分,其本质都是一样的。本人更倾向将其分成字节流和字符流,再在字节流和字符流里面讨论输入流和输出流。
必须要知道的的是,输入输出是相对于“程序”来说的,从物理学方面来讲就是以“程序”为参照物:从程序中流向外部的数据就叫“输出流”,从外部流进程序的数据就叫“输入流”。
输入输出流图
本文主要是对字节流中常见的类做个简要的了解,要想对IO流中所有类有个全面认识的,建议阅读java官方文档。
在字节流中,所有的输入流都直接或间接的继承了InputStream抽象类,输入流作为数据的来源,它提供了read()方法用于读取字节数据;所有的输出流都直接或间接的继承了OutputStream抽象类,输出流作为数据的去向,它也提供了write()方法用于写入字节数据。
InputStream体系常见类
OutputStream体系常见类
正如上图所见的那样,IO流中大部分子类都是以基类为后缀命名的,且都是配套存在的。下面将对这些类进行简单的介绍。
FileInputStream & FileOutputStream
FileInputStream从文件系统中的某个文件中获得输入字节,可以通过File对象或文件路径来初始化;FileOutputStream可以把数据写入某个文件中,也是需要通过File对象或文件路径来初始化。下面用代码来演示这两个类的用法。
一次处理一个字节实现文件的复制:
package com.gk.io.byte_;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileStreamDemo {
public static void main(String[] args) {
copy("sourceFile.txt", "destFile.txt"); // 每次处理一个字节
}
public static void copy(String sourceFile, String destFile) {
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
fileInputStream = new FileInputStream(sourceFile); // 如果指定的文件不存在则抛出FileNotFoundException
fileOutputStream = new FileOutputStream(destFile); // 如果指定的文件不存在会帮我们创建
int len = 0;
while((len = fileInputStream.read()) != -1){ // read()方法如果读到流的末尾,则返回 -1
fileOutputStream.write(len);
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}catch (IOException e) {
throw new RuntimeException(e);
}finally{
if(fileInputStream != null){
try {
fileInputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (fileOutputStream != null){
try {
fileOutputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
上面演示的是一次处理一个字节的操作,虽然可以完成存取,但是速度相对较慢,所以java给我们提供了可以一次存取一个字节数组的操作,数组的大小的就是每次存取的大小,一般是1024个字节或是1024的整数倍。
上面代码对IO流操作中出现的异常的处理是比较规范的,也是推荐使用的,但是在本文下面演示的代码中,为了增加可读性,会直接把异常给抛出去。
public static void copy2(String sourceFile, String destFile) throws IOException{
FileInputStream fileInputStream = new FileInputStream(sourceFile);
FileOutputStream fileOutputStream = new FileOutputStream(destFile);
int len = 0;
byte[] bys = new byte[1024];
/*
* (len = fileInputStream.read(bys)) != -1
* 一次至少读取bys个字节,当读取的数据不足bys字节时,以实际读取的为准
* 返回值len为实际读取的字节长度
* 读到流的末尾时,返回 -1
*/
while((len = fileInputStream.read(bys)) != -1){
fileOutputStream.write(bys, 0, len); // 把len个字节的数据写入destFile中
}
fileInputStream.close();
fileOutputStream.close();
}
由于一次存取了1024个字节大小数据,所以在理论上读取的速度将会比一次存取一个字节的情况快了1024倍。
注意,上面代码每次运行,写入的时候都会覆盖原来的内容。所以FileOutputStream提供了一个可追加写入的构造器FileOutputStream(String name, boolean append),其中append为true时表示追加。
FilterInputStream & FilterOutputStream
单独使用FilterInputStream这个类其实是没多大作用的,其内部只是对InputStream的所有方法进行了简单的重写。在它的构造器中,需要传入一个InputStream对象,然后再调用InputStream相应的方法。我们看一下源码便知。
package java.io;
public class FilterInputStream extends InputStream {
protected volatile InputStream in;
protected FilterInputStream(InputStream in) {
this.in = in;
}
public int read() throws IOException {
return in.read();
}
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
return in.read(b, off, len);
}
public long skip(long n) throws IOException {
return in.skip(n);
}
public int available() throws IOException {
return in.available();
}
public void close() throws IOException {
in.close();
}
public synchronized void mark(int readlimit) {
in.mark(readlimit);
}
public synchronized void reset() throws IOException {
in.reset();
}
public boolean markSupported() {
return in.markSupported();
}
}
可以看到,在FilterInputStream的源码中,只是对InputStream进行了“伪实现”,并不像FileInputStream那样对InputStream进行了实现。那么它有什么作用呢?其实FilterInputStream是用来提供装饰器类接口以控制特定输入流(InputStream)的,这是装饰者模式的体现。它的子类有两个重要的作用,其中DataInputStream可以读取各种基本数据类型和String类型的数据;其他的子类使得它能够对InputStream进行改进,即在原有的InputStream基础上可以提供了新的功能特性。平时用的最多的就是ButtferInputStream,使得inputStream具有缓冲的功能。下面会具体介绍这两个类。
BufferedInputStream & BufferedOutputStream
顾名思义,这两个流都具有缓冲的作用。在BufferedInputStream内部维护有一个字节数组缓冲区buf,每次执行read操作的时候就从这buf中读取数据,从buf中读取数据没有多大的开销。如果buf中已经没有了要读取的数据,那么就去执行其内部绑定的InputStream的read方法,而且是一次性读取很大一块数据,以便填充满buf缓冲区。缓冲区buf的默认大小是8192字节,也就是8K,在构造器中我们也可以自己传入一个size指定缓冲区的大小。由于我们在执行BufferedInputStream的read操作的时候,很多时候都是从缓冲区中读取的数据,这样就大大减少了实际执行其指定的InputStream的read操作的次数,也就提高了读取的效率。我们来看BufferedInputStream类的read()方法源码就知道了。
package java.io;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class BufferedInputStream extends FilterInputStream {
private static int defaultBufferSize = 8192;
protected volatile byte buf[];
private static final
AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater =
AtomicReferenceFieldUpdater.newUpdater
(BufferedInputStream.class, byte[].class, "buf");
protected int count;
protected int pos;
protected int markpos = -1;
protected int marklimit;
public synchronized int read() throws IOException {
if (pos >= count) {
fill();
if (pos >= count)
return -1;
}
return getBufIfOpen()[pos++] & 0xff;
}
private void fill() throws IOException {
byte[] buffer = getBufIfOpen();
if (markpos < 0)
pos = 0; /* no mark: throw away the buffer */
else if (pos >= buffer.length) /* no room left in buffer */
if (markpos > 0) { /* can throw away early part of the buffer */
int sz = pos - markpos;
System.arraycopy(buffer, markpos, buffer, 0, sz);
pos = sz;
markpos = 0;
} else if (buffer.length >= marklimit) {
markpos = -1; /* buffer got too big, invalidate mark */
pos = 0; /* drop buffer contents */
} else { /* grow buffer */
int nsz = pos * 2;
if (nsz > marklimit)
nsz = marklimit;
byte nbuf[] = new byte[nsz];
System.arraycopy(buffer, 0, nbuf, 0, pos);
if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
throw new IOException("Stream closed");
}
buffer = nbuf;
}
count = pos;
int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
if (n > 0)
count = n + pos;
}
private byte[] getBufIfOpen() throws IOException {
byte[] buffer = buf;
if (buffer == null)
throw new IOException("Stream closed");
return buffer;
}
private InputStream getInIfOpen() throws IOException {
InputStream input = in;
if (input == null)
throw new IOException("Stream closed");
return input;
}
}
从上面的源码中看到,这个类有一个buf[],也就是用来当做缓存的字节数组。每次调用read读取数据时,先查看要读取的数据是否在缓存中,如果在缓存中,直接从缓存中读取;如果不在缓存中,则调用fill()方法,从InputStream中读取一定的存储到buf中。
与BufferedInputStream相对的是BufferedOutputStream。在BufferedOutputStream的构造器中我们需要传入一个OutputStream,这样就将BufferedOutputStream与该OutputStream绑定在了一起。BufferedOutputStream内部有一个字节缓冲区buf,在执行write()操作时,将要写入的数据先一起缓存在一起,将其存入字节缓冲区buf中,buf是有限定大小的,默认的大小是8192字节,即8KB,当然也可以在构造器中传入size指定buf的大小。该buf只要被指定了大小之后就不会自动扩容,所以其是有限定大小的,既然有限定大小,就会有被填充完的时刻,当buf被填充完毕的时候会调用BufferedOutputStream的flushBuffer方法,该方法会通过调用其绑定的OutputStream的write方法将buf中的数据进行实际的写入操作并将buf的指向归零(可以看做是将buf中的数据清空)。如果想让缓存区buf中的数据理解真的被写入OutputStream中,可以调用flush方法,flush方法内部会调用flushBuffer方法。由于buf的存在,会大大减少实际执行OutputStream的write操作的次数,优化了写的效率。
下面是BufferedInputStream与BufferedOutputStream的代码演示,实现了一个文件的复制功能。
package com.gk.io.byte_;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class BufferedStreamDemo {
public static void main(String[] args) throws IOException {
copy("sourceFile.txt", "destFile.txt"); // 一次处理一个字节数组
}
public static void copy(String sourceFile, String destFile)
throws IOException {
BufferedInputStream inputStream = new BufferedInputStream(
new FileInputStream(sourceFile));
BufferedOutputStream outputStream = new BufferedOutputStream(
new FileOutputStream(destFile));
int len = 0;
byte[] bys = new byte[1024];
while ((len = inputStream.read(bys)) != -1) {
outputStream.write(bys, 0, len);
}
inputStream.close();
outputStream.close();
}
}
上面代码将FilterInputStream包装到BufferedInputStream中,从而使BufferedInputStream像FilterInputStream一样也具有了读取文件的功能,实现了文件的复制。更重要的是提高了读取的效率。下面代码可以测试这两者的效率。
package com.gk.io.byte_;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class RuntimeDemo {
public static void main(String[] args) throws IOException {
int number = 100; // 记录循环次数
long totalTime = 0; // 记录循环number次所需总时间
for (int x = 1; x <= number; x++) {
long beginTime = System.currentTimeMillis(); // 程序开始运行时间
// 测试文件大小为23276KB
// fun("sourceFile.mkv", "destFile.mkv"); // FileStream一次处理一个字节 --> 运行时间为 : 259163毫秒
// fun2("sourceFile.mkv", "destFile.mkv"); // FileStream一次处理一个字节数组(1024字节) --> 运行时间为 : 322毫秒
// fun3("sourceFile.mkv", "destFile.mkv"); // BufferedStream一次处理一个字节 --> 运行时间为 : 361毫秒
fun4("sourceFile.mkv", "destFile.mkv"); // BufferedStream一次处理一个字节数组(1024字节) --> 运行时间为 : 84毫秒
long endTime = System.currentTimeMillis(); // 程序结束时间
totalTime += endTime - beginTime;
System.out.println("第" + x + "次运行的时间 : " + ((endTime - beginTime)));
}
System.out.println("总时间 : " + totalTime);
System.out.println("平均每次运行时间为 : " + (totalTime / number) + "毫秒"); // 误差允许的范围内不考虑除法的精度问题
}
public static void fun(String sourceFile, String destFile)
throws IOException {
FileInputStream inputStream = new FileInputStream(sourceFile);
FileOutputStream outputStream = new FileOutputStream(destFile);
int len = 0;
while ((len = inputStream.read()) != -1) {
outputStream.write(len);
}
// 释放资源
close(inputStream,outputStream);
}
public static void fun2(String sourceFile, String destFile)
throws IOException {
FileInputStream inputStream = new FileInputStream(sourceFile);
FileOutputStream outputStream = new FileOutputStream(destFile);
int len = 0;
byte[] bys = new byte[1024];
while ((len = inputStream.read(bys)) != -1) {
outputStream.write(bys, 0, len);
}
// 释放资源
close(inputStream,outputStream);
}
public static void fun3(String sourceFile, String destFile)
throws IOException {
BufferedInputStream inputStream = new BufferedInputStream(
new FileInputStream(sourceFile));
BufferedOutputStream outputStream = new BufferedOutputStream(
new FileOutputStream(destFile));
int len = 0;
while ((len = inputStream.read()) != -1) {
outputStream.write(len);
}
// 释放资源
close(inputStream,outputStream);
}
public static void fun4(String sourceFile, String destFile)
throws IOException {
BufferedInputStream inputStream = new BufferedInputStream(
new FileInputStream(sourceFile));
BufferedOutputStream outputStream = new BufferedOutputStream(
new FileOutputStream(destFile));
int len = 0;
byte[] bys = new byte[1024];
while ((len = inputStream.read(bys)) != -1) {
outputStream.write(bys, 0, len);
}
// 释放资源
close(inputStream,outputStream);
}
private static void close(InputStream inputStream, OutputStream outputStream) throws IOException{
if(inputStream != null){
inputStream.close();
}
if(outputStream != null){
outputStream.close();
}
}
}
我测试的文件是一个以.mkv为后缀的音频文件,大小为23276KB(尽量用大一点的文件测试才能更好的看出效果),在我的机器用FileInputStream一次读取一个字节完成复制所需时间是259163毫秒,一次读取一个字节数组(1024字节)所需时间是322毫秒;但是用BufferedInputStream处理却大大缩小了复制的时间,一次读取一个字节的时间是361毫秒,一次读取一个字节数组(1024字节)的时间却只要短短的84毫秒。从对比中我们就可以看出使用BufferedInputStream的好处了。
DataInputStream & DataOutputStream
前面操作的都是字节数据,但有时候我们想操作的是java基本类型的数据,这时候就可以通过DataInputStream的readXXX()方法读取java基本类型的数据和String类型的数据了,这是一些非常有用的方法。如果我们既想要读取基本类型的数据又想提高读取效率的话,我们可以这样使用。
package com.gk.io.byte_;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class DataStreamDemo {
public static void main(String[] args) throws IOException {
write("destFile.txt");
read("destFile.txt");
}
public static void write(String destFile) throws IOException {
DataOutputStream outputStream =
new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream(destFile)));
outputStream.writeBoolean(true);
outputStream.writeByte(47);
outputStream.writeShort(147);
outputStream.writeChar('j');
outputStream.writeInt(520);
outputStream.writeLong(10810810888L);
outputStream.writeFloat(3.14F);
outputStream.writeDouble(2.718281828D);
outputStream.writeUTF("I love java");
// 释放资源
outputStream.close();
}
public static void read(String destFile) throws IOException {
DataInputStream inputStream =
new DataInputStream(
new BufferedInputStream(
new FileInputStream(destFile)));
System.out.println("boolean : " + inputStream.readBoolean());
System.out.println("byte : " + inputStream.readByte());
System.out.println("short : " + inputStream.readShort());
System.out.println("char : " + inputStream.readChar());
System.out.println("int : " + inputStream.readInt());
System.out.println("long : " + inputStream.readLong());
System.out.println("float : " + inputStream.readFloat());
System.out.println("double : " + inputStream.readDouble());
System.out.println("String : " + inputStream.readUTF());
inputStream.close();
}
}
通过将BufferedInputStream包装到DataInputStream中,使DataInputStream具有BufferedInputStream高效的功能。不过要注意读取与写入的顺序要一致。不然会出错。
PrintStream
PrintStream也是FilterOutputStream的直接子类,它为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。提供了print、println等方法方便我们操作,println实现了自动换行。除此之外,构造器PrintStream(OutputStream out, boolean autoFlush)还能实现自动刷新,当autoFlush为true时。
package com.gk.io.byte_;
import java.io.FileNotFoundException;
import java.io.PrintStream;
public class PrintStreamDemo {
public static void main(String[] args) throws FileNotFoundException {
write("printStream.txt");
}
public static void write(String fileName) throws FileNotFoundException {
PrintStream printStream = new PrintStream(fileName);
printStream.println(47); // int
printStream.print(13.14); // double
printStream.println("I love java"); // string
printStream.println(true); // boolean
// 释放资源
printStream.close();
}
}
打印”13.14”的时候使用了print所以没有换行效果。
ByteArrayInputStream & ByteArrayOutputStream
虽然IO流中大部分操作都是针对文件进行的,但有时我们只想把数据保存在内存中,程序结束的时候数据就从内存中消失,这样既能提高程序的运行效率又不会占用太多的存储空间,ByteArrayInputStream和ByteArrayOutputStream就是用来帮助我们操作内存的。
ByteArrayOutputStream其内部有一个字节数组用于存储write()操作时写入的数据,在构造器中可以传入一个size指定其内部的byte数组的大小,如果不指定,那么默认它会将byte数组初始化为32字节,当持续通过write向ByteArrayOutputStream中写入数据时,如果其内部的byte数组的剩余空间不能够存储需要写入的数据,那么那么它会通过调用内部的ensureCapacity(int minCapacity)方法对其内部维护的byte数组进行扩容以存储所有要写入的数据,所以不必担心其内部的byte数组太小导致的IndexOutOfBoundsException之类的异常。
ByteArrayInputStream构造器中需要传入一个byte数组作为数据源,当执行read操作时,就会从该数组中读取数据。
package com.gk.io.byte_;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class ByteArrayStreamDemo {
public static void main(String[] args) throws IOException {
byte[] bys = write();
read(bys);
}
public static byte[] write() throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write("I love java".getBytes());
outputStream.close(); // 关闭此流无效
// 关闭流之后再对其进行操作不会报IOException
outputStream.write("\r\n".getBytes()); // Windows下的回车换行
outputStream.write("I love python too".getBytes());
return outputStream.toByteArray();
}
public static void read(byte[] bys) throws IOException {
ByteArrayInputStream inputStream = new ByteArrayInputStream(bys);
int by = 0;
while((by = inputStream.read()) != -1){
System.out.print((char)by);
}
inputStream.close(); // 关闭此流无效
System.out.println();
System.out.println(inputStream.read()); // 关闭流之后再对其进行操作不会报IOException
}
}
上面代码演示了将一个字符数组写进一个ByteArrayOutputStream中,然后再用toByteArray()将其返回传递给ByteArrayInputStream的构造器,最后ByteArrayInputStream的read()就可以读取数据了。
"\r\n"是Windows系统下的回车换行,也就是说上面的程序使用的时候受到了限制,不能实现平台无关性。在BufferedWriter类中将提供newLine()方法,此方法实现了无关乎平台的换行。
与其他流不同,关闭这两个流是无效的,也就是说关闭之后我们依然可以对其进行操作而不会报IOException。查看源码便知,close()是空现实的。
package java.io;
import java.util.Arrays;
public class ByteArrayOutputStream extends OutputStream {
// ...省略其他源码
public void close() throws IOException {
}
}
ObjectInputStream & ObjectOutputStream
ObjectInputStream和ObjectOutputStream被称为序列化流。和DataOutputStream一样ObjectOutputStream也实现了DataOutput接口,所以也具有一系列的writeXXX方法,可以很方便的向指定的输出流中写入基本类型数据比如writeBoolean、writeChar、writeInt、writeFloat、writeUTF、writeObject等。不过一般用它来操作对象(writeObject()),需要注意的是writeObject()方法中写入的类型必须要实现Serializable接口,从而在执行writeObject()操作时将对象进行序列化成流,并将其写入指定的输出流中;ObjectInputStream是与ObjectOutputStream对应的输入类,具有一系列的readXXX方法(实现了DataInput接口),专门用于读取OutputStream通过writeXXX写入的数据。
实现Serializable接口的Student类
package com.gk.io.bean;
import java.io.Serializable;
public class Student implements Serializable{
/**
*
*/
private static final long serialVersionUID = -8083372836565374164L;
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
对象的存取
package com.gk.io.byte_;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import com.gk.io.bean.Student;
public class ObjectStreamDemo {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
//write("destFile.txt");
read("destFile.txt");
}
public static void write(String destFile) throws FileNotFoundException, IOException {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(destFile));
// 写入对象,如果对象没有实现Serializable接口,则会抛出NotSerializableException
outputStream.writeObject(new Student("zhangSan", 33));
outputStream.writeObject(new Student("liSi", 44));
outputStream.writeObject(new Student("wangWu", 55));
outputStream.close(); // 释放资源
}
public static void read(String destFile) throws FileNotFoundException, IOException, ClassNotFoundException {
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(destFile));
// 读取对象
System.out.println((Student)inputStream.readObject());
System.out.println((Student)inputStream.readObject());
System.out.println((Student)inputStream.readObject());
inputStream.close(); // 释放资源
}
}
在使用ObjectOutputStream时,如果所操作的对象没有实现序列化接口(Serializable)就会抛出NotSerializableException。注意在Student类中有一个序列版本号常量serialVersionUID,它的作用是当我们使用ObjectOutputStream写入对象后,在用ObjectInputStream读取对象之前,允许我们对所操作的对象的字段进行修改而不会有InvalidClassException。读者可以试试在实现序列化接口的实体类中没有添加serialVersionUID的情况下,先用ObjectOutputStream的writeObject()方法写入对象,再修改对象的属性(删除或是修改其修饰符),然后用ObjectInputStream的readObject()读此对象,看看有什么不同。
SequenceInputStream
这个流比较特殊,它不像前面几个流那样有对应的OutputStream。它的作用是将两个或多个InputStream在逻辑上合并为一个InputStream。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。在我们理解了SequenceInputStream的作用是将多个输入流合并为一个输入流之后,我们就能理解为什么不存在对应的SequenceOutputStream类了,因为将一个输出流拆分为多个输出流是没有意义的。
package com.gk.io.byte_;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.util.Vector;
public class SequenceStreamDemo {
public static void main(String[] args) throws IOException {
copy("file1.txt", "file2.txt", "destFile.txt"); // 合并两个流
//copy2("file1.txt", "file2.txt", "file3.txt", "destFile.txt"); // 合并多个流
}
public static void copy(String file1, String file2, String destFile) throws IOException {
@SuppressWarnings("resource")
SequenceInputStream inputStream = new SequenceInputStream(
new FileInputStream(file1),
new FileInputStream(file2));
BufferedOutputStream outputStream = new BufferedOutputStream(
new FileOutputStream(destFile));
int len = 0;
byte[] bys = new byte[1024];
while ((len = inputStream.read(bys)) != -1){
outputStream.write(bys, 0, len);
}
inputStream.close();
outputStream.close();
}
public static void copy2(String file1, String file2, String file3,String destFile) throws IOException {
Vector<InputStream> streams = new Vector<InputStream>();
streams.add(new FileInputStream(file1));
streams.add(new FileInputStream(file2));
streams.add(new FileInputStream(file3));
/*
* 合并多个流的构造器
* public SequenceInputStream(Enumeration<? extends InputStream> e)
*/
SequenceInputStream inputStream = new SequenceInputStream(streams.elements());
BufferedOutputStream outputStream = new BufferedOutputStream(
new FileOutputStream(destFile));
int len = 0;
byte[] bys = new byte[1024];
while ((len = inputStream.read(bys)) != -1){
outputStream.write(bys, 0, len);
}
inputStream.close();
outputStream.close();
}
}
上面代码分别实现了将两个和多个文件的内容复制到一个文件中。虽然可以用FileOutputStream(File file, boolean append)以追加的形式实现,但显然用SequenceInputStream更加便捷。值得注意的是当需要合并多个流的时候SequenceInputStream的构造器需要一个Enumeration接口,所以我们可以将多个流对象放在Vector中,再调用Vector的elements()方法返回Enumeration。
参考资料:
《java编程思想 第4版》
http://blog.csdn.net/iispring/article/details/46821033