JavaIO(1)—OutputStream详解
IO体系
、
- File类。
包里面有一个单独的File类,这个类是文件和目录路径名的抽象表示形式。这个类里面包含了很多与文件或路径有关的方法,如:创建和删除文件或者路径,获取文件或路径的属性,判断文件或路径是否具有一些性质等。尽管输入输出设备有很多,但是操作最多的还是硬盘,而数据在硬盘上的表现形式就是文件,即File。
- OutputStream及其子类:输出字节流。这个体系将数据按照字节格式写入到输出设备(硬盘或屏幕等)。
- InputStream及其子类:输入字节流。这个体系读取不同输入设备(键盘,硬盘等)的字节数据源。
- Writer及其子类:输出字符流。这个体系将数据按照字符格式写入到输出设备(硬盘或屏幕等)。
- Reader及其子类:输入字符流。这个体系将数据按照字符格式写入到输出设备(硬盘或屏幕等)。
二,IO中的“装饰者模式”
本文先讲OutputStream字节输出流体系,上面是这个体系的继承关系图。其中1,2,3,4是OutputStream的直接子类,他们分别实现了父类的write()等方法,而且写的形式和目的地各不相同,用来完成不同的任务。还有一个直接子类FilterOutputStream,它及其子类5,6,7就构成了“装饰者模式”,比如子类5:BufferedOutputStream,它可以接受1,2,3,4等直接子类,完成特定任务的同时,使用了缓冲区,提高了效率。
源码分析
File类的使用很简单,不再赘述。本文说一下OutputStream字节输出流及其子类的部分源码,以及“装饰者模式”的应用。
OutputStream类源码
package java.io;
// OutputStream是所有字节输出流的超类,并实现2个接口,这两个接口种分别有一个方法close()和flush()
public abstract class OutputStream implements Closeable, Flushable {
/*
将指定字节写入此文件输出流,子类需实现该方法。write 的常规协定是:向输出流写入一个字节。要写入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。
*/
public abstract void write(int b) throws IOException;
// 将 b.length 个字节从指定 byte 数组写入此文件输出流中。
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
// 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流。
public void write(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
for (int i = 0 ; i < len ; i++) {
write(b[off + i]); // 去调用wirte(int b)方法一个一个写
}
}
/* 刷新此输出流并强制写出所有缓冲的输出字节。flush 的常规协定是:如果此输出流的实现已经缓冲了以前写入的任何字节,则调用此方法指示应将这些字节立即写入它们预期的目标。*/
public void flush() throws IOException {
}
/*
关闭此输出流并释放与此流有关的所有系统资源。close 的常规协定 是:该方法将关闭输出流。关闭的流不能执行输出操作,也不能重新打开。
*/
public void close() throws IOException {
}
}
FileOutputStream源码
package java.io;
import java.nio.channels.FileChannel;
import sun.nio.ch.FileChannelImpl;
/**
* 文件输出流用于将字节数据写入到文件中。
*
* 该流可以用作写图像数据。要写字符,则考虑FileWriter
*
* 是OutputStream的直接子类
*/
public class FileOutputStream extends OutputStream
{
/**
* 打开文件句柄
*/
private final FileDescriptor fd;
/**
* 是否在文件末尾追加
*/
private final boolean append;
/**
* 引用对象
*/
private FileChannel channel;
/**
* 文件路径
*/
private final String path;
private final Object closeLock = new Object();
private volatile boolean closed = false;
// 构造方法1,传入表示文件路径的字符串,将字符串初始化为File对象,传给构造方法4
public FileOutputStream(String name) throws FileNotFoundException {
this(name != null ? new File(name) : null, false);
}
// 构造方法2,传入表示文件路径的字符串和是否追加标识,将字符串初始化为File对象,传给构造方法4
public FileOutputStream(String name, boolean append)
throws FileNotFoundException
{
this(name != null ? new File(name) : null, append);
}
// 构造方法3,传入File对象,调用构造方法4
public FileOutputStream(File file) throws FileNotFoundException {
this(file, false);
}
// 构造方法4,前3个构造方法都调用这个构造方法
public FileOutputStream(File file, boolean append)
throws FileNotFoundException
{
String name = (file != null ? file.getPath() : null);
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkWrite(name);
}
if (name == null) {
throw new NullPointerException();
}
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
this.fd = new FileDescriptor();
fd.attach(this);
this.append = append;
this.path = name;
open(name, append); // open调用native方法,打开指定的文件
}
// 创建一个向指定文件描述符处写入数据的输出文件流
public FileOutputStream(FileDescriptor fdObj) {
SecurityManager security = System.getSecurityManager();
if (fdObj == null) {
throw new NullPointerException();
}
if (security != null) {
security.checkWrite(fdObj);
}
this.fd = fdObj;
this.append = false;
this.path = null;
fd.attach(this);
}
// 实现父类的write方法
public void write(int b) throws IOException {
write(b, append); // 调用native方法,一次写一个字节。
}
// 重写了父类的write(byte[])方法
public void write(byte b[]) throws IOException {
writeBytes(b, 0, b.length, append); // 调用native方法,写字节数组
}
// 重写了父类的write方法
public void write(byte b[], int off, int len) throws IOException {
writeBytes(b, off, len, append); // 调用native方法,写字节数组一部分
}
// 重写了父类的close()方法,同样调用native方法实现资源关闭
public void close() throws IOException {
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}
if (channel != null) {
channel.close();
}
fd.closeAll(new Closeable() {
public void close() throws IOException {
close0();
}
});
}
// flush继承自父类,父类的flush方法啥都不做,自然FileOutputStream的flush方法也是啥也不做。
ByteArrayOutputStream和ObjectOutputStream等
这2个直接子类和其他直接子类同样实现了特定的write()方式,代码不再贴出来了。
FilterOutputStream抽象装饰者类
package java.io;
public class FilterOutputStream extends OutputStream {
protected OutputStream out; // 持有父类的对象
public FilterOutputStream(OutputStream out) {
this.out = out; // 构造方法传入父类对象
}
public void write(int b) throws IOException {
out.write(b); // 调用父类的同名方法
}
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
public void write(byte b[], int off, int len) throws IOException {
if ((off | len | (b.length - (len + off)) | (off + len)) < 0)
throw new IndexOutOfBoundsException();
for (int i = 0 ; i < len ; i++) {
write(b[off + i]);
}
}
public void flush() throws IOException {
out.flush(); // 调用父类的同名方法
}
@SuppressWarnings("try")
public void close() throws IOException {
try (OutputStream ostream = out) {
flush();
}
}
}
### BufferedOutputStream具体装饰者类
该类是继承自FilterOutputStream,自然也就继承了父类的OutputStream类型成员变量out。也就可以为OutputStream的直接子类提供特定的“装饰”,即缓冲区。
package java.io;
public class BufferedOutputStream extends FilterOutputStream {
protected byte buf[]; // 内部缓冲区
protected int count; // 缓冲区中的有效字节数
public BufferedOutputStream(OutputStream out) {
this(out, 8192); // 构造具有OutputStream对象和固定大小缓冲区的对象
}
public BufferedOutputStream(OutputStream out, int size) {
super(out); // 调用父类的构造器传入OutputStream对象
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size]; // 初始化自定义大小的缓冲区
}
private void flushBuffer() throws IOException { // 刷新缓冲区方法
if (count > 0) {
out.write(buf, 0, count);
count = 0;
}
}
/*
BufferedOutputStream的write()方法底层虽然也是调用了OutputStream的write()方法,但是这个write()方法在OutputStream的write()方法基础上添加了缓存的功能,即对原write()方法进行了“装饰”,从而提高了效率。
*/
public synchronized void write(int b) throws IOException {
if (count >= buf.length) {
flushBuffer(); // 缓冲区满了就刷新
}
buf[count++] = (byte)b; // 缓存字节数据
}
// 同理,新write()方法对老write()方法进行了“装饰”,即功能的增强。
public synchronized void write(byte b[], int off, int len) throws IOException {
if (len >= buf.length) {
/* If the request length exceeds the size of the output buffer,
flush the output buffer and then write the data directly.
In this way buffered streams will cascade harmlessly. */
flushBuffer();
out.write(b, off, len);
return;
}
if (len > buf.length - count) {
flushBuffer();
}
System.arraycopy(b, off, buf, count, len);
count += len;
}
public synchronized void flush() throws IOException {
flushBuffer();
out.flush();
}
}
DataOutputStream和PrintStream等具体装饰者类。
同BufferedOutputStream对OutputStream进行“缓冲区装饰”,还有其他的很多具体装饰者类对OutputStream进行各种各样的“装饰”。如:DataOutputStream以适当方式将基本 Java 数据类型写入输出流中,PrintStream为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。
代码不再贴出来了。
总结
现在,我们总结了OutputStream体系的所有类,发现如下几条规律:
- OutputStream作为超类,定义了该体系最基本的方法。
- OutputStream的直接子类(除了FilterOutputStream),实现了各种情况下所需的write()方式。
- FilterOutputStream类及其子类是对2中各直接子类的“装饰”,目的是提供额外的功能
Tips
FileOutputStream流顺序地写文件,只要不关闭流,每次调用write()都会顺序地向文件写入内容,直到关闭。如果要写入的文件不存在,它将首先创建文件,然后再写,如果文件存在,则刷新文件中的内容,再顺序写入。所以,如果不想文件被刷新,可以
OutputStream output2 = new FileOutputStream(file,true);
//第二个参数true表示不刷新文件,接着上次的写.
或 OutputStream bufferoutput = new FileOutputStream(file,true);
文件分隔符. File.separator
File file = new File(“E:\java”+ File.separator+”test.txt”);
Byte[]转String
String result = new String(resultByte);//将byte数组转化为String
String 转 Byte[]
byte[] testByte = “helloworld”.getBytes();// 将字符串转化为byte数组