java -- IO流之字节流

 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),其中appendtrue时表示追加。


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中已经没有了要读取的数据,那么就去执行其内部绑定的InputStreamread方法,而且是一次性读取很大一块数据,以便填充满buf缓冲区。缓冲区buf的默认大小是8192字节,也就是8K,在构造器中我们也可以自己传入一个size指定缓冲区的大小。由于我们在执行BufferedInputStreamread操作的时候,很多时候都是从缓冲区中读取的数据,这样就大大减少了实际执行其指定的InputStreamread操作的次数,也就提高了读取的效率。我们来看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被填充完毕的时候会调用BufferedOutputStreamflushBuffer方法,该方法会通过调用其绑定的OutputStreamwrite方法将buf中的数据进行实际的写入操作并将buf的指向归零(可以看做是将buf中的数据清空)。如果想让缓存区buf中的数据理解真的被写入OutputStream中,可以调用flush方法,flush方法内部会调用flushBuffer方法。由于buf的存在,会大大减少实际执行OutputStreamwrite操作的次数,优化了写的效率。 

 

 下面是BufferedInputStreamBufferedOutputStream的代码演示,实现了一个文件的复制功能。


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中,从而使BufferedInputStreamFilterInputStream一样也具有了读取文件的功能,实现了文件的复制。更重要的是提高了读取的效率。下面代码可以测试这两者的效率。


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基本类型的数据,这时候就可以通过DataInputStreamreadXXX()方法读取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的直接子类,它为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。提供了printprintln等方法方便我们操作,println实现了自动换行。除此之外,构造器PrintStream(OutputStream out, boolean autoFlush)还能实现自动刷新,当autoFlushtrue时。


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流中大部分操作都是针对文件进行的,但有时我们只想把数据保存在内存中,程序结束的时候数据就从内存中消失,这样既能提高程序的运行效率又不会占用太多的存储空间,ByteArrayInputStreamByteArrayOutputStream就是用来帮助我们操作内存的。

 

 ByteArrayOutputStream其内部有一个字节数组用于存储write()操作时写入的数据,在构造器中可以传入一个size指定其内部的byte数组的大小,如果不指定,那么默认它会将byte数组初始化为32字节,当持续通过writeByteArrayOutputStream中写入数据时,如果其内部的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的构造器,最后ByteArrayInputStreamread()就可以读取数据了。

 

 "\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


 ObjectInputStreamObjectOutputStream被称为序列化流。和DataOutputStream一样ObjectOutputStream也实现了DataOutput接口,所以也具有一系列的writeXXX方法,可以很方便的向指定的输出流中写入基本类型数据比如writeBooleanwriteCharwriteIntwriteFloatwriteUTFwriteObject等。不过一般用它来操作对象(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的情况下,先用ObjectOutputStreamwriteObject()方法写入对象,再修改对象的属性(删除或是修改其修饰符),然后用ObjectInputStreamreadObject()读此对象,看看有什么不同。


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中,再调用Vectorelements()方法返回Enumeration




参考资料:

《java编程思想 第4版》

http://blog.csdn.net/iispring/article/details/46821033

http://blog.csdn.net/zhao123h/article/details/52826682


































發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章