OutputStream抽象类是所有输出字节流的超类,输出流接收输出字节,并将这些字节发送到某个接收器。这个接收器可以是字节数组、文件、管道。该类的定义如下:
1 public abstract class OutputStream implements Closeable, Flushable {
2 //将指定的字节写到这个输出流中
3 public abstract void write(int b) throws IOException;
4 //将指定字节数组中的内容写到输出流中
5 public void write(byte b[]) throws IOException {
6 write(b, 0, b.length);
7 }
8 public void write(byte b[], int off, int len) throws IOException {
9 if (b == null) {
10 throw new NullPointerException();
11 } else if ((off < 0) || (off > b.length) || (len < 0) ||
12 ((off + len) > b.length) || ((off + len) < 0)) {
13 throw new IndexOutOfBoundsException();
14 } else if (len == 0) {
15 return;
16 }
17 for (int i = 0 ; i < len ; i++) {
18 write(b[off + i]);
19 }
20 }
21 //清理输出流中的数据,迫使缓冲的字节写出去
22 public void flush() throws IOException {
23 }
24 //关闭流
25 public void close() throws IOException {
26 }
27 }
输出字节流的类结构图如下,同样,这里只列举常用的几个类,还有很多未被列出。
下面对不同的输出流进行简单的分析,会给出相应的类源码和示例。
1、ByteArrayOutputStream,字节数组输出流,此类实现了一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray()
和 toString()
获取数据。 源代码如下:
1 import java.util.Arrays;
2 public class ByteArrayOutputStream extends OutputStream {
3 //定义一个用于存储输出数据的缓冲数组
4 protected byte buf[];
5 protected int count;
6 public ByteArrayOutputStream() {
7 this(32);
8 }
9 public ByteArrayOutputStream(int size) {
10 if (size < 0) {
11 throw new IllegalArgumentException("Negative initial size: "+ size);
12 }
13 buf = new byte[size];
14 }
15 private void ensureCapacity(int minCapacity) {
16 // overflow-conscious code
17 if (minCapacity - buf.length > 0)
18 grow(minCapacity);
19 }
20 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
21 //扩展缓冲数组大小
22 private void grow(int minCapacity) {
23 // overflow-conscious code
24 int oldCapacity = buf.length;
25 int newCapacity = oldCapacity << 1;
26 if (newCapacity - minCapacity < 0)
27 newCapacity = minCapacity;
28 if (newCapacity - MAX_ARRAY_SIZE > 0)
29 newCapacity = hugeCapacity(minCapacity);
30 buf = Arrays.copyOf(buf, newCapacity);
31 }
32 private static int hugeCapacity(int minCapacity) {
33 if (minCapacity < 0) // overflow
34 throw new OutOfMemoryError();
35 return (minCapacity > MAX_ARRAY_SIZE) ?
36 Integer.MAX_VALUE :
37 MAX_ARRAY_SIZE;
38 }
39 //将数字b写到缓冲数组中
40 public synchronized void write(int b) {
41 ensureCapacity(count + 1);
42 buf[count] = (byte) b;
43 count += 1;
44 }
45 public synchronized void write(byte b[], int off, int len) {
46 if ((off < 0) || (off > b.length) || (len < 0) ||
47 ((off + len) - b.length > 0)) {
48 throw new IndexOutOfBoundsException();
49 }
50 ensureCapacity(count + len);
51 System.arraycopy(b, off, buf, count, len);
52 count += len;
53 }
54 //将此 byte 数组输出流的全部内容写入到指定的输出流参数中,
55 //这与使用 out.write(buf, 0, count) 调用该输出流的 write 方法效果一样。
56 public synchronized void writeTo(OutputStream out) throws IOException {
57 out.write(buf, 0, count);
58 }
59 public synchronized void reset() {
60 count = 0;
61 }
62 //将输出的内容以字符数组的形式返给用户
63 public synchronized byte toByteArray()[] {
64 return Arrays.copyOf(buf, count);
65 }
66 public synchronized int size() {
67 return count;
68 }
69 //将输出的内容以字符串的形式返给用户
70 public synchronized String toString() {
71 return new String(buf, 0, count);
72 }
73 //将输出的内容以以指定字符编码的字符串形式返给用户
74 public synchronized String toString(String charsetName)
75 throws UnsupportedEncodingException
76 {
77 return new String(buf, 0, count, charsetName);
78 }
79 @Deprecated
80 public synchronized String toString(int hibyte) {
81 return new String(buf, hibyte, 0, count);
82 }
83 public void close() throws IOException {
84 }
85 }
从源码可以看出,涉及到数据操作的方法都加了synchronized关键字,所以该类是安全同步的类。使用方法如下:
1 static void byteArrayOutputTest(){
2 ByteArrayOutputStream out=new ByteArrayOutputStream(8);
3 try{
4 while(out.size()!=8){
5 out.write(System.in.read());
6 }
7 for(byte by:out.toByteArray()){
8 System.out.print((char)by+" ");
9 }
10 System.out.println();
11 }catch(Exception e){
12 e.printStackTrace();
13 }
14 }
我们创建了一个字节数组输出流,它的缓冲容量大小为8,然后我们从控制台进行输入,输入的时候可以不加空格,如果添加空格,空格也计数在内,可以输入多个字符,但最后输出的字符数只有8个,因为我们已经指定了缓冲容量的大小,当用toByteArray()方法取出数据时,它返回的字符数组长度为8.
2、FileOutputStream,文件输出流,它是将数据输出到文件中,注意这里操作对象——文件的可用与否与平台有关,某些平台一次只允许一个 FileOutputStream(或其他文件写入对象)打开文件进行写入。在这种情况下,如果所涉及的文件已经打开,则此类中的构造方法将失败。
1 import java.nio.channels.FileChannel;
2 import sun.nio.ch.FileChannelImpl;
3 public class FileOutputStream extends OutputStream
4 {
5 private final FileDescriptor fd;
6 private final boolean append;
7 private FileChannel channel;
8 private final String path;
9 private final Object closeLock = new Object();
10 private volatile boolean closed = false;
11 //创建文件输出流,如果文件不存在,则自动创建文件
12 public FileOutputStream(String name) throws FileNotFoundException {
13 this(name != null ? new File(name) : null, false);
14 }
15 //创建文件输出流,如果文件不存在,则自动创建文件
16 //同时指定文件是否具有追加内容的功能,如果有,则新添内容放到文件后面,而不覆盖源文件内容
17 public FileOutputStream(String name, boolean append)
18 throws FileNotFoundException
19 {
20 this(name != null ? new File(name) : null, append);
21 }
22 public FileOutputStream(File file) throws FileNotFoundException {
23 this(file, false);
24 }
25 //创建文件输出流,并指定可以在文件最后追加内容,如果没有指定,则将原来内容覆盖
26 public FileOutputStream(File file, boolean append)
27 throws FileNotFoundException
28 {
29 String name = (file != null ? file.getPath() : null);
30 SecurityManager security = System.getSecurityManager();
31 if (security != null) {
32 security.checkWrite(name);
33 }
34 if (name == null) {
35 throw new NullPointerException();
36 }
37 if (file.isInvalid()) {
38 throw new FileNotFoundException("Invalid file path");
39 }
40 this.fd = new FileDescriptor();
41 fd.attach(this);
42 this.append = append;
43 this.path = name;
44 open(name, append);
45 }
46 public FileOutputStream(FileDescriptor fdObj) {
47 SecurityManager security = System.getSecurityManager();
48 if (fdObj == null) {
49 throw new NullPointerException();
50 }
51 if (security != null) {
52 security.checkWrite(fdObj);
53 }
54 this.fd = fdObj;
55 this.append = false;
56 this.path = null;
57 fd.attach(this);
58 }
59 private native void open0(String name, boolean append)
60 throws FileNotFoundException;
61 private void open(String name, boolean append)
62 throws FileNotFoundException {
63 open0(name, append);
64 }
65 //调用本地方法,将内容写入指定文件
66 private native void write(int b, boolean append) throws IOException;
67 public void write(int b) throws IOException {
68 write(b, append);
69 }
70 private native void writeBytes(byte b[], int off, int len, boolean append)
71 throws IOException;
72 //将字符数组内容写进文件
73 public void write(byte b[]) throws IOException {
74 writeBytes(b, 0, b.length, append);
75 }
76 public void write(byte b[], int off, int len) throws IOException {
77 writeBytes(b, off, len, append);
78 }
79 //关闭文件流
80 public void close() throws IOException {
81 synchronized (closeLock) {
82 if (closed) {
83 return;
84 }
85 closed = true;
86 }
87 if (channel != null) {
88 channel.close();
89 }
90
91 fd.closeAll(new Closeable() {
92 public void close() throws IOException {
93 close0();
94 }
95 });
96 }
97 public final FileDescriptor getFD() throws IOException {
98 if (fd != null) {
99 return fd;
100 }
101 throw new IOException();
102 }
103 public FileChannel getChannel() {
104 synchronized (this) {
105 if (channel == null) {
106 channel = FileChannelImpl.open(fd, path, false, true, append, this);
107 }
108 return channel;
109 }
110 }
111 //清除文件流缓冲内容,并关闭文件流
112 protected void finalize() throws IOException {
113 if (fd != null) {
114 if (fd == FileDescriptor.out || fd == FileDescriptor.err) {
115 flush();
116 } else {
117 close();
118 }
119 }
120 }
121 private native void close0() throws IOException;
122 private static native void initIDs();
123 static {
124 initIDs();
125 }
126 }
文件输出流操作不是线程安全的,如果用于多线程访问,注意使用同步。以下是文件输出流操作的例子:
1 //文件输出流测试
2 static void fileOutputTest(){
3 FileOutputStream fout=null;
4 FileInputStream fin=null;
5 try{
6 //从my.java文件中读取内容
7 fin=new FileInputStream("my.java");
8 fout=new FileOutputStream("out.txt");
9 byte[] buff=new byte[1024];
10 while(fin.read(buff)>0){
11 //FileInputStream将从my.java文件中读取到的内容写到buff数组中
12 //然后FileOutputStream将buff数组中的内容写到流中
13 fout.write(buff);
14 }//将流中缓冲的内容输出到文件中
15 fout.flush();
16 }catch(Exception e){
17 e.printStackTrace();
18 }finally{
19 try{
20 if(fout!=null)
21 fout.close();
22 }catch(Exception e){
23 e.printStackTrace();
24 }
25 }
26 }
为了减少操作,这里使用了文件输入流对象,我们从my.java文件中读取内容,然后将读取到的内容写到out.txt文件中,从my.java文件中读取内容要用输入流,向文件中写内容要用输出流,这里两种流都做了使用。
3、FilterOutputStream,该类是提供输出流的装饰器类的接口,继承该类的子类相当于一个装饰器,能够为OutputStream类型的对象操作提供额外的功能,这里以BufferedOutputStream为例,该类实现缓冲的输出流。通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必针对每次字节写入调用底层系统。 比如,我们需要向一个文件中输入内容,这时候我们可以先将内容存储到缓冲数组中,并不是真的向该文件写内容,当调用flush()方法或关闭流时,内容才真正写到文件中。该类的源码如下:
1 public class BufferedOutputStream extends FilterOutputStream {
2 //内部存储数据的缓冲数组
3 protected byte buf[];
4 //缓冲中的有效字节数目
5 protected int count;
6 //创建一个缓冲输出流,将时数据写到特定的底层输出流中
7 public BufferedOutputStream(OutputStream out) {
8 this(out, 8192);
9 }
10 //创建一个缓冲输出流,将时数据写到具有特定大小容量的特定的底层输出流中
11 public BufferedOutputStream(OutputStream out, int size) {
12 super(out);
13 if (size <= 0) {
14 throw new IllegalArgumentException("Buffer size <= 0");
15 }
16 buf = new byte[size];
17 }
18 //将缓冲中的数据清理出去,输出到目的地
19 private void flushBuffer() throws IOException {
20 if (count > 0) {
21 out.write(buf, 0, count);
22 count = 0;
23 }
24 }
25 //将指定的字节写到缓冲输出流中
26 public synchronized void write(int b) throws IOException {
27 if (count >= buf.length) {
28 flushBuffer();
29 }
30 buf[count++] = (byte)b;
31 }
32 //从指定位置off开始,将b数组内的len个字节写到缓冲输出流中
33 //一般来说,此方法将给定数组的字节存入此流的缓冲区中,根据需要将该缓冲区刷新,并转到底层输出流。
34 //但是,如果请求的长度至少与此流的缓冲区大小相同,则此方法将刷新该缓冲区并将各个字节直接写入底层输出流。因此多余的 BufferedOutputStream 将不必复制数据。
35 public synchronized void write(byte b[], int off, int len) throws IOException {
36 if (len >= buf.length) {
37 flushBuffer();
38 out.write(b, off, len);
39 return;
40 }
41 if (len > buf.length - count) {
42 flushBuffer();
43 }
44 System.arraycopy(b, off, buf, count, len);
45 count += len;
46 }
47 //刷新此缓冲的输出流。这迫使所有缓冲的输出字节被写出到底层输出流中。
48 public synchronized void flush() throws IOException {
49 flushBuffer();
50 out.flush();
51 }
52 }
从源码来看,可以得知,该类也是线程同步的,所以在多线程环境下能够保证访问的正确性。下面给出该类的一个示例:
1 //缓冲输出流测试
2 static void bufferedOutputTest(){
3 BufferedOutputStream bof=null;
4 BufferedInputStream bin=null;
5 FileOutputStream fout=null;
6 FileInputStream fin=null;
7 byte[] buff=new byte[1024];
8 try{
9 fin=new FileInputStream("my.java");
10 bin=new BufferedInputStream(fin);
11 fout=new FileOutputStream("out.txt");
12 bof=new BufferedOutputStream(fout);
13 while(fin.read(buff)>0){
14 bof.write(buff,0,buff.length);
15 }
16 //如果将下面这一行注释掉,而且有没有将bof关掉,则out.txt文件中不会有内容出现
17 //因为内容还在缓冲中,还未并没有输出到文本上
18 bof.flush();
19 }catch(Exception e){
20 e.printStackTrace();
21 }finally{
22 try{
23 if(bof!=null)
24 bof.close();
25 if(fout!=null)
26 fout.close();
27 if(fin!=null)
28 fin.close();
29 }catch(Exception e){
30 e.printStackTrace();
31 }
32 }
33 }
该示例同样用了输入流,为了操作方便,我们直接向一个文件中读取数据,所以要用输入流,然后将读取到的数据输出到另一个文件中,如果out.txt文件不存在,则会自动创建该文件,在输出数据时,可以利用flush函数及时将缓冲数组中的内容输出到文件中。
4、DataOutputStream,数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后,应用程序可以使用数据输入流将数据读入。下面是该类源码:
1 //DataOutputStream允许应用程序将基本的java数据类型写入到一个输出流中,
2 //同时应用程序还可以用一个DataInputStream将数据读回。
3 public class DataOutputStream extends FilterOutputStream implements DataOutput {
4 //记录当前写到数据输入流中的字节数
5 protected int written;
6 //bytearr根据请求,由writeUTF进行初始化
7 private byte[] bytearr = null;
8 public DataOutputStream(OutputStream out) {
9 super(out);
10 }
11 private void incCount(int value) {
12 int temp = written + value;
13 if (temp < 0) {//输入字节数操作能够表示的最大数
14 temp = Integer.MAX_VALUE;
15 }
16 written = temp;
17 }
18 //将指定的字节b写到底层的输出流
19 public synchronized void write(int b) throws IOException {
20 out.write(b);
21 incCount(1);
22 }
23 public synchronized void write(byte b[], int off, int len)
24 throws IOException
25 {
26 out.write(b, off, len);
27 incCount(len);
28 }
29 //清理输出流,迫使所有缓冲的输出字节被写出到流中。
30 public void flush() throws IOException {
31 out.flush();
32 }
33 //将值为ture的boolean类型的数据以1的形式记录
34 public final void writeBoolean(boolean v) throws IOException {
35 out.write(v ? 1 : 0);
36 incCount(1);
37 }
38 //将一个 byte 值以 1-byte 值形式写出到基础输出流中
39 public final void writeByte(int v) throws IOException {
40 out.write(v);
41 incCount(1);
42 }
43 public final void writeShort(int v) throws IOException {
44 out.write((v >>> 8) & 0xFF);
45 out.write((v >>> 0) & 0xFF);
46 incCount(2);
47 }
48 public final void writeChar(int v) throws IOException {
49 out.write((v >>> 8) & 0xFF);
50 out.write((v >>> 0) & 0xFF);
51 incCount(2);
52 }
53 public final void writeInt(int v) throws IOException {
54 out.write((v >>> 24) & 0xFF);
55 out.write((v >>> 16) & 0xFF);
56 out.write((v >>> 8) & 0xFF);
57 out.write((v >>> 0) & 0xFF);
58 incCount(4);
59 }
60 private byte writeBuffer[] = new byte[8];
61 public final void writeLong(long v) throws IOException {
62 writeBuffer[0] = (byte)(v >>> 56);
63 writeBuffer[1] = (byte)(v >>> 48);
64 writeBuffer[2] = (byte)(v >>> 40);
65 writeBuffer[3] = (byte)(v >>> 32);
66 writeBuffer[4] = (byte)(v >>> 24);
67 writeBuffer[5] = (byte)(v >>> 16);
68 writeBuffer[6] = (byte)(v >>> 8);
69 writeBuffer[7] = (byte)(v >>> 0);
70 out.write(writeBuffer, 0, 8);
71 incCount(8);
72 }
73 public final void writeFloat(float v) throws IOException {
74 writeInt(Float.floatToIntBits(v));
75 }
76 public final void writeDouble(double v) throws IOException {
77 writeLong(Double.doubleToLongBits(v));
78 }
79 public final void writeBytes(String s) throws IOException {
80 int len = s.length();
81 for (int i = 0 ; i < len ; i++) {
82 out.write((byte)s.charAt(i));
83 }
84 incCount(len);
85 }
86 public final void writeChars(String s) throws IOException {
87 int len = s.length();
88 for (int i = 0 ; i < len ; i++) {
89 int v = s.charAt(i);
90 out.write((v >>> 8) & 0xFF);
91 out.write((v >>> 0) & 0xFF);
92 }
93 incCount(len * 2);
94 }
95 public final void writeUTF(String str) throws IOException {
96 writeUTF(str, this);
97 }
98 public final int size() {
99 return written;
100 }
101 }
从源码可以看出,该类也是线程安全的。具体的使用方法通过一个示例来了解,如下:
1 static void dataOutputTest(){
2 DataOutputStream dout=null;
3 FileOutputStream fout=null;
4 DataInputStream din=null;
5 FileInputStream fin=null;
6 int[] arr={1,2,3,4,5};
7 try{
8 fout=new FileOutputStream("out.txt");
9 dout=new DataOutputStream(fout);
10 for(int val:arr){
11 //将数据读入到文件中
12 dout.writeInt(val);
13 }
14 //从文件中读出数据
15 fin=new FileInputStream("out.txt");
16 din=new DataInputStream(fin);
17 while(din.available()>0){
18 System.out.print(din.readInt()+" ");
19 }
20 }catch(Exception e){
21 e.printStackTrace();
22 }finally{
23 try{
24 if(fin!=null)
25 fin.close();
26 if(fout!=null)
27 fout.close();
28 if(din!=null)
29 din.close();
30 if(dout!=null)
31 dout.close();
32 }catch(Exception e){
33 e.printStackTrace();
34 }
35 }
36 }
我们可以向文件中写入基本的java数据,也可以相应地读出这些数据,读出数据同样用到了输入流。
5、PrintStream,打印流,同样也是一个装饰器,可以为其他输出流添加功能,PrintStream
打印的所有字符都使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter
类。我们用的最多的输出就是System.out.println()方法,直接将内容输出到控制台上,其中,System是一个包含几个静态变量和静态方法的类,它不能够被实例化,其中一个静态变量out就是PrintStream类型的对象,使用System.out.println默认将输出送到控制台上,我们也可以将内容输出到一个文件中,如下例子:
1 static void printStreamTest(){
2 PrintStream pri=null;
3 try{
4 pri=new PrintStream("out.txt");
5 pri.print("hello,world");
6 pri.println();
7 pri.println(12);
8 pri.close();
9 }catch(Exception e){
10 e.printStackTrace();
11 }
12 }
我们可以利用println方法,将内容输出到一个文件中,其实这就是装饰器的功能,它增强了FileOutputStream的功能(注意,我们这里是将内容输出到一个文件中,所以PrintStream类内部调用的是FileOutputStream对象,这里不再给出源码),使得我们的输入更加简单。
以上是输出流的总结,总的来说,理解输出流类的继承关系和使用关系很重要,只有这样才能知道如何使用输出流。