Java IO 之一:传统IO (1)InputStream,OutputStream

InputStream,OutputStream

IO是最常用的组件之一,也想写一篇关于IO的,总结下,也方便自己查找,主要分三个部分来写,1、传统IO,2、NIO ,3、Java7中的NIO.2

首先是传统的IO操作,这是Java IO的基础,在Java中,所有的IO操作以流的方式存在,如果从流的角度来分来,在我们使用的时候,分为输出流/输入流,字节流/字符流,节点流/处理流。其中输出流和输入流不用多说,指的就是数据的流向,要从硬盘中读入内存,那必然是输入流,要从内存中写到硬盘等外部设备中,那就是输出流。

字节流/字符流:其实两者的区别在于其数据单元的大小上,字节流,每次处理的都是8bit的字节,而字符流,处理的都是16bit的字符,因此我们可以容易理解,在处理汉字的时候,大多数用的字符流而并非字节流。

节点流/处理流:简单的理解,节点流是一种较低级别的数据流,是直接从硬盘、网络等I/O设备中获取数据的流,处理流是节点流的一个封装,通过对节点流的封装,可以大大简化我们在操作Java的IO的时候的编程的复杂性。同时,在面向对象设计中,使用处理流可以使我们的程序能够更加容易的适配不同的I/O设备,降低程序对I/O具体操作的依赖。

Java的I/O有4个处理流的基类,InputStream、OutputStream、Reader、Writer,在该4个基类的基础之上派生出很多的子类,我用下面这样一张图来梳理一下。

InputStream

InputStream

 

先看看InputStream

public abstract class InputStream
extends Object
implements Closeable

This abstract class is the superclass of all classes representing an input stream of bytes.

Applications that need to define a subclass of InputStream must always provide a method that returns the next byte of input.

InputStream是所有输入字节流的父类,而要继承InputStream必须实现一个返回下一个字节的方法,同时我们也注意到,在Java 7中,该类实现了Closeable接口,所以我们可以直接在try后面打开文件,从而减少了手动关闭的麻烦。既然是所有字节输入流的父类,那我们就看一看里面声明的主要的方法有那些。

Modifier and Type Method and Description
int available() 

Returns an estimate of the number of bytes that can be read (or skipped over) from this input stream without blocking by the next invocation of a method for this input stream.
void close() 

Closes this input stream and releases any system resources associated with the stream.
void mark(int readlimit) 

Marks the current position in this input stream.
boolean markSupported() 

Tests if this input stream supports the mark and reset methods.
abstract int read() 

Reads the next byte of data from the input stream.
int read(byte[] b) 

Reads some number of bytes from the input stream and stores them into the buffer array b.
int read(byte[] b, int off, int len) 

Reads up to len bytes of data from the input stream into an array of bytes.
void reset() 

Repositions this stream to the position at the time the mark method was last called on this input stream.
long skip(long n) 

Skips over and discards n bytes of data from this input stream.

我们查看源文件也可以发现,InputStream中,有一个抽象方法 read(),而read(byte[] b,),read(byte[] b, int off, int len)都在read()方法上实现。然而向其他的available()、close()、mark()、等等都是需要在子类中去重载的。

OutputStream

OutputStream

OutputStream与InputStream一样,是基于字节流的处理,是ByteArrayOutputStream, FileOutputStream, FilterOutputStream, ObjectOutputStream, OutputStream, PipedOutputStream的父类,其中提供了

void close() 

Closes this output stream and releases any system resources associated with this stream.
void flush() 

Flushes this output stream and forces any buffered output bytes to be written out.
void write(byte[] b) 

Writes b.length bytes from the specified byte array to this output stream.
void write(byte[] b, int off, int len) 

Writes len bytes from the specified byte array starting at offset off to this output stream.
abstract void write(int b) 

Writes the specified byte to this output stream.

(一)AudioInputStream

该类是音频输入流,我们一般很少用到,这里不是讨论的重点。

(二)ByteArrayInputStream && ByteArrayOutputStream这两个方法是将数组封装成Stream的类。

package InputStream;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class TestByteArrayStream {
	public static void main(String[] args) throws IOException
	{
		//Default Constructor:Size is set to 32 On OpenJDK7 / Ubuntu12.04
		ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
		for(int i=0;i<100;i++)
			byteArrayOutputStream.write(i);
		try {
			FileOutputStream fileOutputStream=new FileOutputStream(new File("TestByteArrayStream.data"));
			byteArrayOutputStream.writeTo(fileOutputStream);
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		byte data[]=new byte[100];
		for(byte i=0;i<100;i++)
			data[i]=i;
		ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(data);
		int tmp;
		//the next byte of data, or -1 if the end of the stream has been reached.
		while((tmp=byteArrayInputStream.read())!=-1)
		{
			System.out.print(tmp+" ");
			if(byteArrayInputStream.available()==6)
			{
				byteArrayInputStream.skip(10);
			}
		}

		byteArrayInputStream.close();
		byteArrayOutputStream.close();
	}
}

该两个类比较简单,即使将数组这一节点流封装成处理流丢给上层处理,需要注意的是,可以发现在ByteArrayInputStream或者ByteArrayOutputStream中,关于读、写方法都是同步的,可以保证数据的完整性。

(三)FileInputStream\FileOutputStream

package InputStream;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class TestFileStream {
	public static void main(String[] args) throws IOException 
	{
		try {
			FileInputStream fileInputStream=new FileInputStream("src/InputStream/TestFileStream.java");
			byte buf[]=new byte[1024];
			FileOutputStream fileOutputStream=new FileOutputStream("TestFileStream.data");
			while(fileInputStream.available()>0)
			{
				int count=fileInputStream.read(buf);
				fileOutputStream.write(buf,0,count);
			}
			fileInputStream.close();
			fileOutputStream.close();
		}catch (FileNotFoundException e) {
			// TODO: handle exception
			e.printStackTrace();
		}catch (IOException e)
		{
			e.printStackTrace();
		}
	}

}

(四)FilterInputStream\FilterOutputStream

从上面的类的继承关系上来看,都有很多子类,我们来看一下这两个类的作用。

FilterInputStream
A FilterInputStream contains some other input stream, which it uses as its basic source of data, possibly transforming the data along the way or providing additional functionality. The class FilterInputStream itself simply overrides all methods of InputStream with versions that pass all requests to the contained input stream. Subclasses of FilterInputStream may further override some of these methods and may also provide additional methods and fields.
FilterOutputStream
This class is the superclass of all classes that filter output streams. These streams sit on top of an already existing output stream (the underlying output stream) which it uses as its basic sink of data, but possibly transforming the data along the way or providing additional functionality.
The class FilterOutputStream itself simply overrides all methods of OutputStream with versions that pass all requests to the underlying output stream. Subclasses ofFilterOutputStream may further override some of these methods as well as provide additional methods and fields.

从Java的API文档中我们可以看到,这两个类只是实现了对InputStream和OutputStream的简单封装,在类里面都分别定义了一个protected Inputstream in;protected OutputStream out;通过这两个类及其子类,我们可以更好的优化程序数据流通路。

<1>BufferedInputStream/BufferedOutputStream

A BufferedInputStream adds functionality to another input stream-namely, the ability to buffer the input and to support the mark and reset methods. When the BufferedInputStream is created, an internal buffer array is created. As bytes from the stream are read or skipped, the internal buffer is refilled as necessary from the contained input stream, many bytes at a time. The mark operation remembers a point in the input stream and the reset operation causes all the bytes read since the most recent mark operation to be reread before new bytes are taken from the contained input stream.

顾名思义,BufferedInputStream就是将我们(三)FileInputStream\FileOutputStream中写的程序那样,在外面使用的buffer缓冲区,内建在类中,查看源文件我们可以看到,默认的buffer大小为8192byte,同时还提供了一个mark和reset的方法,因此,要搞明白这两个类的使用方法,我们需要好好注意这两个方法的使用,下面是一个小小的demo。

package InputStream;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class TestBufferedStream {
	public static void main(String[] args)
	{
		try {
			FileInputStream fileInputStream=new FileInputStream("src/InputStream/TestBufferedStream.java");
			FileOutputStream fileOutputStream=new FileOutputStream("TestBufferedStream.data");

			BufferedInputStream bufferedInputStream=new BufferedInputStream(fileInputStream);
			BufferedOutputStream bufferedOutputStream=new BufferedOutputStream(fileOutputStream);

			int times=0;
			int marktimes=0;
			int count=0;
			bufferedInputStream.mark(1000);
			while(bufferedInputStream.available()>0)
			{
				byte[] buff;
				if(marktimes<3)
					buff=new byte[1000];
				else{
					if(marktimes==3)
						bufferedInputStream.mark(1000);
					buff=new byte[8192];
				}
				marktimes++;
				int counttmp=bufferedInputStream.read(buff);
				count=counttmp+count;
				//for(byte tmp:buff)
				//	System.out.print(String.valueOf(tmp));
				bufferedOutputStream.write(buff,0,counttmp);
				if(bufferedInputStream.available()<100 && times<5)
				{
					System.out.println("\n上一次读取: "+count+" bytes");
					count=0;
					bufferedInputStream.reset();
					times++;
				}
			}
			System.out.println("\n上一次读取: "+count+" bytes");

			//注意!应该先关闭BufferedInputStream/BufferedOutputStream,在关闭InputStream/OutStream
			//或者只关闭BufferedStream即刻,因为在BufferedStream的close方法中,会将构造函数中传入的InputStream/OutputStream自动关闭。
			bufferedInputStream.close();
			bufferedOutputStream.close();
			//fileInputStream.close();
			//fileOutputStream.close();

		}catch (FileNotFoundException e) {
			// TODO: handle exception
			e.printStackTrace();
		}catch (IOException e)
		{
			e.printStackTrace();
		}
	}

}

比较简单,就不加注释了,主要是讲讲其中的mark(int readlimit)其中的readlimit – the maximum limit of bytes that can be read before the mark position becomes invalid.就是说,在读到readlimit个字节之前,可以保证该mark有效的。

mark函数和reset()函数是一起的,当mark之后,调用reset函数,则再读取数据的时候,读取到的就是mark时所在的位置,上图。同时要注意到,在mark函数运行之前,不能调用reset函数,否则会引发IOException错误(IOException – if this stream has not been marked or, if the mark has been invalidated, or the stream has been closed by invoking its close() method, or an I/O error occurs.)

同时,我们还应该注意到的时候,我们使用处理流之后,就不需要去关闭我们包装的流,当我们直接关闭处理流的时候,传入的被包装的流会被自动关闭。

上述程序的运行结果为

上一次读取: 1987 bytes
上一次读取: 1987 bytes
上一次读取: 987 bytes
上一次读取: 987 bytes
上一次读取: 987 bytes
上一次读取: 987 bytes

最初的时候,我们设置的mark为开始的位置,所以那时我们调用reset,便会回到文章开头,所以我们看到执行的结果中,头两次都是1987 bytes,当我们在文件读写过程中设置标签的时候,可以发现,此时读取便会从我们设置标签的位置开始,程序中设置的是1000,我们查看生成的文件,也是这样的效果。

(2)CheckedInputStream/CheckedOutputStream

CheckedInputStream:An input stream that also maintains a checksum of the data being read. The checksum can then be used to verify the integrity of the input data.

CheckedOutputStream:An output stream that also maintains a checksum of the data being written. The checksum can then be used to verify the integrity of the output data.

CheckedInputStream和CheckedInputStream的源码在 java/util/zip/目录下,其作用是为了校验数据的准确行,其构造函数为

CheckedInputStream(InputStream in, Checksum cksum)
Creates an input stream using the specified Checksum.

CheckedOutputStream(OutputStream out, Checksum cksum)
Creates an output stream with the specified Checksum.

需要我们传入InputStream/OutputStream流,和一个Checksum接口,查看zip下的源文件,发现Java已经为我们写好了两个可以用的继承了Checksum接口的源程序。Adler32/CRC32,我们以CRC32为例,来看看怎么使用。

package InputStream;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.zip.CRC32;
import java.util.zip.CheckedInputStream;
import java.util.zip.CheckedOutputStream;

public class TestCheckedStream{
	public static void main(String[] args)
	{
		FileInputStream fileInputStream;
		FileOutputStream fileOutputStream;

		CheckedInputStream checkedInputStream;
		CheckedOutputStream checkedOutputStream;

		CRC32 crc32=new CRC32();

		try{
			fileInputStream=new FileInputStream("src/InputStream/TestCheckedStream.java");
			fileOutputStream=new FileOutputStream("TestCheckedStream.data");

			checkedInputStream=new CheckedInputStream(fileInputStream,crc32);
			checkedOutputStream=new CheckedOutputStream(fileOutputStream, crc32);

			System.out.println("CheckedInputStream:CheckSum:"+checkedInputStream.getChecksum().getValue());
			System.out.println("CheckedOutStream:CheckSum:"+checkedOutputStream.getChecksum().getValue());
			byte[] buffer=new byte[1024];
			while(checkedInputStream.available()>0)
			{
				int count=checkedInputStream.read(buffer);
				checkedOutputStream.write(buffer, 0, count);
			}
			System.out.println("CheckedInputStream:CheckSum:"+checkedInputStream.getChecksum().getValue());
			System.out.println("CheckedOutStream:CheckSum:"+checkedOutputStream.getChecksum().getValue());

			checkedInputStream.close();
			checkedOutputStream.close();
		}
		catch(Exception ex)
		{

		}

	}
}

运行程序,得到结果为:

CheckedInputStream:CheckSum:0
CheckedOutStream:CheckSum:0
CheckedInputStream:CheckSum:280008503
CheckedOutStream:CheckSum:280008503

可以发现,CheckedSum是根据流过的数据流来计算的。在实际使用中,我们可以使用这个办法来简单的判断文件是否完整。

(3)CipherInputStream/CipherOutputStream

该子类是Java中的加密类

A CipherInputStream is composed of an InputStream and a Cipher so that read() methods return data that are read in from the underlying InputStream but have been additionally processed by the Cipher. The Cipher must be fully initialized before being used by a CipherInputStream.

之前用过,回去翻翻代码,再补充一下相关的内容,可单独作为一篇来仔细理解。

(4)DataInputStream/DataOutputStream

该两个类主要的作用在于帮助我们省去格式转换的麻烦,我们可以直接将特定格式的内容写入输出流或者从输入流中读到信息,使用起来也比较简单,直接上代码吧。

package InputStream;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;

import javax.swing.text.StyledEditorKit.ForegroundAction;

public class TestDataStream {
	public static void main(String[] args)
	{
		ArrayList<Fruit> arrayList=new ArrayList<Fruit>();
		for(int i=0;i<10;i++)
		{
			Fruit tmp=new Fruit("Apple"+i, i);
			arrayList.add(tmp);
		}

		try{
			FileOutputStream fileOutputStream=new FileOutputStream("TestDataStream.data");
			DataOutputStream dataOutputStream=new DataOutputStream(fileOutputStream);

			for(Fruit tmp:arrayList)
			{
				dataOutputStream.writeUTF(tmp.kind);
				dataOutputStream.writeInt(tmp.price);
			}

			FileInputStream fileInputStream=new FileInputStream("TestDataStream.data");
			DataInputStream dataInputStream=new DataInputStream(fileInputStream);
			while(dataInputStream.available()>0){
				System.out.println("Get: Kind:"+dataInputStream.readUTF()+" Price:"+dataInputStream.readInt());
			}

			dataInputStream.close();
			dataOutputStream.close();
		}
		catch(Exception ex)
		{
			ex.printStackTrace();
		}

	}
}

class Fruit{
	public String kind;
	public int price;
	public Fruit(String kind,int price){
		this.kind=kind;
		this.price=price;
	}
}

程序的运行结果为:

Get: Kind:Apple0 Price:0
Get: Kind:Apple1 Price:1
Get: Kind:Apple2 Price:2
Get: Kind:Apple3 Price:3
Get: Kind:Apple4 Price:4
Get: Kind:Apple5 Price:5
Get: Kind:Apple6 Price:6
Get: Kind:Apple7 Price:7
Get: Kind:Apple8 Price:8
Get: Kind:Apple9 Price:9

至于文件内容中具体是怎么保存的,我们不去做仔细的追求。

(5)DeflaterInputStream/InflaterOutputStream   AND  InflaterInputStream/DeflaterOutputStream

这是两组输入输出流,其实都是用的DEFLATE的加密解密算法,具体的大家可以参阅维基百科  https://zh.wikipedia.org/wiki/DEFLATE,使用这两组输入输出流,可以直接在输出的时候将我们的数据进行加密、解密。这里我们使用 InflaterInputStream/DeflaterOutputStream来实验以下其用法,我们选择一个文件进行读取,压缩,解压。好了,开工。

package InputStream;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.Deflater;
import java.util.zip.DeflaterInputStream;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import java.util.zip.InflaterOutputStream;

public class TestDeflaterStream {
	public static void main(String[] args){
		try{
			File file=new File("src/InputStream/TestDeflaterStream.java");
			FileInputStream fileInputStream=new FileInputStream(file);
			FileOutputStream fileOutputStream=new FileOutputStream("TestDeflaterStream.data");

			Deflater deflater=new Deflater();
			deflater.setLevel(Deflater.BEST_COMPRESSION);
			DeflaterOutputStream deflaterOutputStream=new DeflaterOutputStream(fileOutputStream, deflater);

			byte[] buffer=new byte[8192];
			System.out.println("文件 原始长度:"+file.length());
			while(fileInputStream.available()>0){
				int count=fileInputStream.read(buffer);
				deflaterOutputStream.write(buffer, 0, count);
			}

			deflaterOutputStream.close();
			fileInputStream.close();

			file=new File("TestDeflaterStream.data");
			fileInputStream=new FileInputStream(file);
			InflaterInputStream inflaterInputStream=new InflaterInputStream(fileInputStream);

			fileOutputStream=new FileOutputStream("TestDeflaterStream.java.data");
			System.out.println("压缩后文件长度:"+file.length());
			while(inflaterInputStream.available()>0){
				int chardata=inflaterInputStream.read();
				fileOutputStream.write(chardata);
			}

			fileOutputStream.close();
			inflaterInputStream.close();

		}
		catch(FileNotFoundException ex)
		{
			ex.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

程序会加密后保存到一个文件中 TestDeflaterStream.data中,然后解密,放入TestDeflaterStream.java.data

文件大小:

文件 原始长度:1893
压缩后文件长度:622

对于文本文档,压缩比还是很不错的,当然,这里选择的是最高的压缩比。

(6)LineNumberInputStream

该类在Java中已经是被标志为deprecation,是因为不能准确的对字符统计行数,更好的方法是使用character流的类。如下所说:

This class incorrectly assumes that bytes adequately represent characters. As of JDK 1.1, the preferred way to operate on character streams is via the new character-stream classes, which include a class for counting line numbers.

 而且提供的功能也比较简单,

This class is an input stream filter that provides the added functionality of keeping track of the current line number.

A line is a sequence of bytes ending with a carriage return character ('\r'), a newline character ('\n'), or a carriage return character followed immediately by a linefeed character. In all three cases, the line terminating character(s) are returned as a single newline character.

只是增加了一个行的标记,比较简单,不多探究。

(7)PushbackInputStream

PushbackInputStream adds functionality to another input stream, namely the ability to “push back” or “unread” one byte. This is useful in situations where it is convenient for a fragment of code to read an indefinite number of data bytes that are delimited by a particular byte value; after reading the terminating byte, the code fragment can “unread” it, so that the next read operation on the input stream will reread the byte that was pushed back. For example, bytes representing the characters constituting an identifier might be terminated by a byte representing an operator character; a method whose job is to read just an identifier can read until it sees the operator and then push the operator back to be re-read.

顾名思义,该类的主要目的在于帮助我们,可以将读过的数据再推送回去,下次读数据的时候,这些被推送回去的数据就可以被读到。用个小例子来看看其用法。

package InputStream;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.LineNumberInputStream;
import java.io.PushbackInputStream;

public class TestPushBackInputStream {
	public static void main(String[] args){

		try{
			FileInputStream fileInputStream=new FileInputStream("src/InputStream/TestPushBackInputStream.java");
			PushbackInputStream pushbackInputStream=new PushbackInputStream(fileInputStream,1024);  //默认情况下,buf为1

			byte[] buffer=new byte[10];
			int count=0;
			boolean status=false;
			while(pushbackInputStream.available()>0)
			{
				int tmpcount=pushbackInputStream.read(buffer);
				count=tmpcount+count;
				if(count>=20 && !status)
				{
					pushbackInputStream.unread(buffer);
					status=true;
				}
				System.out.print(new String(buffer));
			}

		}
		catch(FileNotFoundException ex)
		{
			ex.printStackTrace();
		}
		catch (IOException e) {
			// TODO: handle exception
			e.printStackTrace();
		}

	}
}

在文章中,buffer大小为10,当读了两次之后,退回去一次,所以看到运行结果为

package InputStream;putStream;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.LineNumberInputStream;
...

特别注意第一行,发现确实是有效的,同时,我们要注意到一个问题,在程序中,我有这样一条注释

PushbackInputStream pushbackInputStream=new PushbackInputStream(fileInputStream,1024);  //默认情况下,buf为1

最开始的时候,我调用的构造函数是

PushbackInputStream pushbackInputStream=new PushbackInputStream(fileInputStream);

即不指定buffer大小,但后来发现一个问题,在unread调用的时候,只能操作一个字节,否则就会报错:

java.io.IOException: Push back buffer is full
	at java.io.PushbackInputStream.unread(Unknown Source)
	at java.io.PushbackInputStream.unread(Unknown Source)
	at InputStream.TestPushBackInputStream.main(TestPushBackInputStream.java:25)

最早的时候,以为程序写的有问题,后来查看 PushbackInputStream.java的源代码才发现,其不指定buffer大小的时候,传入的buffer大小为:

    public PushbackInputStream(InputStream in) {
        this(in, 1);
    }

    public PushbackInputStream(InputStream in, int size) {
        super(in);
        if (size <= 0) {
            throw new IllegalArgumentException("size <= 0");
        }
        this.buf = new byte[size];
        this.pos = size;
    }

好了,这就明白了,buffer大小默认为1,我那样用当然会出错啦,这里也给大家提个醒,一定要好好注意。

 

(五)ObjectInputStream/ObjectOutputStream

对于ObjectInputStream和ObjectOuptStream主要是用于Java中对象序列化,单独关于Java的序列化就要探究好长时间,这里不多做分析,留着,等有时间了,专门好好从源代码看看这一部分内容。http://blog.happyhls.me/?p=368

(六)PipedInputStream/PipedOutputStream

管道的概念,就是水管一样,尤其是在不同线程的时候,我这个线程放水,然后另外一个线程用来接水,我们看看怎么用就可以,在程序中,我开了两个线程,分别是Sender和Receiver,然后将其中的PipedInputStream和PipedOutputStrem链接到一起。

代码如下:

package InputStream;

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;

public class TestPipedStream {
	public static void main(String[] args) {
		Sender sender = new Sender();
		Receiver receiver = new Receiver();
		try {
			sender.getPipedOutputStrem().connect(receiver.getPipedInputStrem());

			new Thread(sender).start();
			new Thread(receiver).start();

		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

class Sender extends Thread {
	private PipedOutputStream pipedOutputStream = new PipedOutputStream();

	public PipedOutputStream getPipedOutputStrem() {
		return pipedOutputStream;
	}

	public void run() {
		while (true) {
			try {
				System.out.println(System.currentTimeMillis()
						+ ":Piped 准备写入数据...即将阻塞进程");
				pipedOutputStream.write("发送数据Sender".getBytes());
				System.out.println(System.currentTimeMillis()
						+ ":Piped 写入数据完毕...休眠1s");
				Thread.sleep(2000);
			} catch (IOException e) {
				e.printStackTrace();
			} catch (InterruptedException e) {
				// TODO: handle exception
				e.printStackTrace();
			}
		}
	}
}

class Receiver extends Thread {
	private PipedInputStream pipedInputStream = new PipedInputStream();

	public PipedInputStream getPipedInputStrem() {
		return pipedInputStream;
	}

	public void run() {
		while (true) {
			try {
				System.out.println(System.currentTimeMillis()
						+ ":Piped 等待数据中...即将阻塞进程");
				byte[] buffer = new byte[8192];
				int count = pipedInputStream.read(buffer);
				System.out.println(System.currentTimeMillis() + ":接收到数据:"
						+ new String(buffer, 0, count));
				Thread.sleep(1000);
			} catch (IOException e) {
				e.printStackTrace();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

运行结果:

1365048086265:Piped 等待数据中...即将阻塞进程
1365048086265:Piped 准备写入数据...即将阻塞进程
1365048086265:Piped 写入数据完毕...休眠1s
1365048087265:接收到数据:发送数据Sender
1365048088265:Piped 准备写入数据...即将阻塞进程
1365048088265:Piped 写入数据完毕...休眠1s
1365048088265:Piped 等待数据中...即将阻塞进程
1365048088265:接收到数据:发送数据Sender

(七)SequenceInputStream

SequenceInputStream represents the logical concatenation of other input streams. It starts out with an ordered collection of input streams and reads from the first one until end of file is reached, whereupon it reads from the second one, and so on, until end of file is reached on the last of the contained input streams.

SequenceInputStream是将多个输入流合并到一起,形成一个输入流,然后我们就可以对这一个输入流进行访问,从而大大方便了我们的编程。

我们看SequenceInputStream的构造函数

SequenceInputStream(Enumeration<? extends InputStream> e)
Initializes a newly created SequenceInputStream by remembering the argument, which must be an Enumeration that produces objects whose run-time type is InputStream.
SequenceInputStream(InputStream s1, InputStream s2)
Initializes a newly created SequenceInputStream by remembering the two arguments, which will be read in order, first s1 and then s2, to provide the bytes to be read from this SequenceInputStream.

可以看到,只要是InputStream的子类,那我们都可以使用这个累,将其合成一个输入流,从而进行读写,这里我们写这样一个demo

package InputStream;

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;

public class TestSequenceInputStream {

	public static void main(String[] args) throws Exception
	{

		ArrayList<InputStream> inputStreams=new ArrayList<InputStream>();
		byte[] tmp=new byte[100];
		for(byte i=0;i<100;i++)
		{
			tmp[i]=i;
		}
		inputStreams.add(new ByteArrayInputStream(tmp));
		inputStreams.add(new FileInputStream("src/InputStream/TestSequenceInputStream.java"));

		Enumeration<InputStream> enumeration=Collections.enumeration(inputStreams);
		SequenceInputStream sequenceInputStream=new SequenceInputStream(enumeration);

		byte[] buffer=new byte[1024];
		while(true){
			int count=sequenceInputStream.read(buffer);
			if(count==-1)
				break;
			System.out.println(new String(buffer,0,count));
		}
	}
}

需要注意的一个问题是,在下面的程序段中:

		while(true){
			int count=sequenceInputStream.read(buffer);
			if(count==-1)
				break;
			System.out.println(new String(buffer,0,count));
		}

最初使用的是之前的方法:

while(sequenceInputStrem.available()>0)

来判断是否还有数据,但在这段的执行过程中,发现第二个数据流不会被读取,但在之前的程序中运行一直正常,我的理解是因为,SequenceInputStream包装了多个InputStream,但 available()函数的时候,调用的是第一个输入流中的available函数,所以会出现退出循环的问题,为了验证这个想法,修改了一下代码

		byte[] buffer=new byte[1024];
		while(sequenceInputStream.available()>0){
			int count=sequenceInputStream.read(buffer);
			System.out.println(new String(buffer,0,count));
		}
		sequenceInputStream.read();//多读数据一次
		while(sequenceInputStream.available()>0){
			int count=sequenceInputStream.read(buffer);
			System.out.println(new String(buffer,0,count));
		}

看到这句了吧,当两个数据流切换的时候,让其多读了一次,如果不加这一语句,程序依然只打印第一个InputStream流的信息

		sequenceInputStream.read();//多读数据一次

加入这句之后,执行正常,当然,会丢失下一个输入流的第一个字符数据。当然可以保存下来,但还是比较麻烦,还是用count是否为-1来判断是否到输入流的结尾比较妥当。

(八) StringBufferInputStream

该输入流也被取消掉了,看定义是将字符串转化为输入流的类,既然JDK都摒弃了,那我们也就不做探究,如果用到,再来仔细查看。

This class allows an application to create an input stream in which the bytes read are supplied by the contents of a string. Applications can also read bytes from a byte array by using a ByteArrayInputStream.

(九) PrintStream

PrintStream在Java中是一个非常重要的处理流,要知道,我们常用的System.out其本质上就是封装了个PrintStream,该类非常强大,支持各种格式化输出,使用起来比较简单,而且由于System.out的缘故,我们用的也比较熟悉,有什么新颖的功能,用到的时候再添加进来。

 

 

 

About: happyhls


发表评论

电子邮件地址不会被公开。 必填项已用*标注