Java IO的实现

从一个方法看javaIO:

    public byte[] compress(IWritable value) {
        if (value == null) return null;
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            DataOutputStream out = new DataOutputStream(bos);
            value.writeFields(out);
            out.close();
            return bos.toByteArray();
        } catch(Exception e) {
            LOG.error("Cannot compress value. " + value, e);
            return null;
        }
    }

首先ByteArrayOutputStream创建了一个OutputStream,然后被DataOutputStream包裹了,之后往DataOutputStream里写数据。写完之后,DataOutputStream关闭了,但是ByteArrayOutputStream依然可以将刚刚写入的数据取出来。

OutputStream & ByteArrayOutputStream

这里,先看一下ByteArrayOutputStream,它继承了OutputStream。OutputStream值得我们记住:

public abstract class OutputStream
extends Object
implements Closeable, Flushable

This abstract class is the superclass of all classes representing an output stream of bytes. An output stream accepts output bytes and sends them to some sink.

Applications that need to define a subclass of OutputStream must always provide at least a method that writes one byte of output.

因为它是所有输出流的爸爸。比如ByteArrayOutputStream,从名字看它是一个字节数组(byte array)输出流,从实现来看,可以发现它内部有一个protected byte buf[],这个buf字节数组就是真正放字节的地方。它也必须实现爸爸的抽象方法write(int),要不然就丧失了往buf里写byte的基本功能:

    /**
     * Writes the specified byte to this byte array output stream.
     *
     * @param   b   the byte to be written.
     */
    public synchronized void write(int b) {
        ensureCapacity(count + 1);
        buf[count] = (byte) b;
        count += 1;
    }

拓展:FileOutputStream

OutputStream的另一个常见子类是FileOutputStream:

public class FileOutputStream
extends OutputStream

A file output stream is an output stream for writing data to a File or to a FileDescriptor. Whether or not a file is available or may be created depends upon the underlying platform. Some platforms, in particular, allow a file to be opened for writing by only one FileOutputStream (or other file-writing object) at a time. In such situations the constructors in this class will fail if the file involved is already open.

FileOutputStream is meant for writing streams of raw bytes such as image data. For writing streams of characters, consider using FileWriter.

旨在写bytes到文件,用于写图片之类的。其存放数据的地方是文件:

    /**
     * The system dependent file descriptor.
     */
    private final FileDescriptor fd;

    /**
     * True if the file is opened for append.
     */
    private final boolean append;

    /**
     * The associated channel, initialized lazily.
     */
    private FileChannel channel;

    /**
     * The path of the referenced file
     * (null if the stream is created with a file descriptor)
     */
    private final String path;

写的真正实现write(int)是和系统相关的:

    /**
     * Writes the specified byte to this file output stream. Implements
     * the <code>write</code> method of <code>OutputStream</code>.
     *
     * @param      b   the byte to be written.
     * @exception  IOException  if an I/O error occurs.
     */
    public void write(int b) throws IOException {
        write(b, append);
    }

    /**
     * Writes the specified byte to this file output stream.
     *
     * @param   b   the byte to be written.
     * @param   append   {@code true} if the write operation first
     *     advances the position to the end of file
     */
    private native void write(int b, boolean append) throws IOException;

注意:OutputStream是和写byte相关的,想写字符可以参考另一个爸爸:Writer。

FilterOutputStream

然后再看DataOutputStream:

public class DataOutputStream
extends FilterOutputStream
implements DataOutput

A data output stream lets an application write primitive Java data types to an output stream in a portable way. An application can then use a data input stream to read the data back in.

继承FilterOutputStream,并实现DataOutput接口。

先看它继承的FilterOutputStream。这也是一个值得我们记住的类,因为他也是爸爸:

public class FilterOutputStream
extends OutputStream

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 of FilterOutputStream may further override some of these methods as well as provide additional methods and fields.

它是所有的在输出流上加过滤器的类的爸爸。它自己不提供输出流,而是在内部包装了一个输出流:

    /**
     * The underlying output stream to be filtered.
     */
    protected OutputStream out;

这个输出流就是由提供输出流的类(OutputStream的子类)来提供,比如上文刚刚介绍的ByteArrayOutputStream。

也为此,所有FilterOutPutStream的子类也必有一个构造函数,传入一个输出流:

    /**
     * Creates an output stream filter built on top of the specified
     * underlying output stream.
     *
     * @param   out   the underlying output stream to be assigned to
     *                the field <tt>this.out</tt> for later use, or
     *                <code>null</code> if this instance is to be
     *                created without an underlying stream.
     */
    public FilterOutputStream(OutputStream out) {
        this.out = out;
    }

(目前看来,好像也仅有这么一个构造函数。之所以不存在无参构造函数,是因为这种对象为过滤输出流而生,如果不传入一个输出流,那还过滤谁去?所以这算是一种强制措施)

我们还可以在FilterOutputStream的介绍中看到,这个类的初衷是这样的:FilterOutputStream只是简单地实现了OutputStream的方法(实现的方式是交由底层的输出流去实现。。。果然是“简单地”实现了。。。)比如,看看它怎么实现OutputStream的write(int)的:

    /**
     * Writes the specified <code>byte</code> to this output stream.
     * <p>
     * The <code>write</code> method of <code>FilterOutputStream</code>
     * calls the <code>write</code> method of its underlying output stream,
     * that is, it performs <tt>out.write(b)</tt>.
     * <p>
     * Implements the abstract <tt>write</tt> method of <tt>OutputStream</tt>.
     *
     * @param      b   the <code>byte</code>.
     * @exception  IOException  if an I/O error occurs.
     */
    public void write(int b) throws IOException {
        out.write(b);
    }

就是让底层的输出流去write(int)。(out是刚刚介绍的FilterOutputStream封装的输出流)

而它的子类就要做更多了,需要提供额外的功能。现在回首刚刚的DataOutputStream。

DataOutputStream

从名字来看,这个wrapper类是为了写data的,看类声明(上文),它可以写原生Java数据类型(primitive Java data type)到输出流。具体一看它包含的方法就知道:write(int)/writeBoolean/writeByte/writeInt/writeLong/writeUTF/writeDouble等等,果然是把Java的原生类型的写入方法都实现了(这些都是对接口DataOutput的实现)。看一个writeInt

    /**
     * Writes an <code>int</code> to the underlying output stream as four
     * bytes, high byte first. If no exception is thrown, the counter
     * <code>written</code> is incremented by <code>4</code>.
     *
     * @param      v   an <code>int</code> to be written.
     * @exception  IOException  if an I/O error occurs.
     * @see        java.io.FilterOutputStream#out
     */
    public final void writeInt(int v) throws IOException {
        out.write((v >>> 24) & 0xFF);
        out.write((v >>> 16) & 0xFF);
        out.write((v >>>  8) & 0xFF);
        out.write((v >>>  0) & 0xFF);
        incCount(4);
    }

就是写一个int类型,4 bytes,共32bits,先写高8bits(24~31bit),再写16~23bit,再写8~15bit,最后写低8位,0~7bit。而写入的地方,就是它封装的那个OutputStream对象。

所以继承了FilterOutputStream的wrapper类,都是给简单的输出流包装上了一层新功能的。

拓展:BufferedOutputStream

可以再举个例子,BufferedOutputStream:

public class BufferedOutputStream
extends FilterOutputStream

The class implements a buffered output stream. By setting up such an output stream, an application can write bytes to the underlying output stream without necessarily causing a call to the underlying system for each byte written.

看名字,大概是用来做缓存的。看类介绍,其旨在给输出流搞个缓存,这样就不用每次写入输出流的时候,都要调用一次底层调用。

具体怎么搞的呢?首先,自然是先放一个缓存:

    /**
     * The internal buffer where data is stored.
     */
    protected byte buf[];

    /**
     * The number of valid bytes in the buffer. This value is always
     * in the range <tt>0</tt> through <tt>buf.length</tt>; elements
     * <tt>buf[0]</tt> through <tt>buf[count-1]</tt> contain valid
     * byte data.
     */
    protected int count;

在初始化BufferedOutputStream的时候,除了要指定底层的输出流,还要指定缓存的大小,大小 默认是8192:

    /**
     * Creates a new buffered output stream to write data to the
     * specified underlying output stream.
     *
     * @param   out   the underlying output stream.
     */
    public BufferedOutputStream(OutputStream out) {
        this(out, 8192);
    }

    /**
     * Creates a new buffered output stream to write data to the
     * specified underlying output stream with the specified buffer
     * size.
     *
     * @param   out    the underlying output stream.
     * @param   size   the buffer size.
     * @exception IllegalArgumentException if size &lt;= 0.
     */
    public BufferedOutputStream(OutputStream out, int size) {
        super(out);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }

再看BufferedOutputStream的write(int)

    /**
     * Writes the specified byte to this buffered output stream.
     *
     * @param      b   the byte to be written.
     * @exception  IOException  if an I/O error occurs.
     */
    public synchronized void write(int b) throws IOException {
        if (count >= buf.length) {
            flushBuffer();
        }
        buf[count++] = (byte)b;
    }

    /** Flush the internal buffer */
    private void flushBuffer() throws IOException {
        if (count > 0) {
            out.write(buf, 0, count);
            count = 0;
        }
    }

每次都是往buffer里写,只有满了之后才刷入输出流,确实减少了对底层写入的调用。

结语

以上介绍的是爸爸OutputStream这一套家底,和写byte相关。其包装类的爸爸是FilterOutputStream

读byte可以参考爸爸InputStream那一套东西,和包装类爸爸FilterInputStream

写字符可以参考爸爸Writer那一套东西,和包装类爸爸FilterWriter

读字符可以参考爸爸Reader那一套东西,和包装类爸爸FilterReader

其实记住几个爸爸,关系就理清了。

这里也涉及到一个很好的设计模式:装饰者模式,有兴趣可以了解一下。

DataOutput

最后说一下DataOutput这个接口:

public interface DataOutput

The DataOutput interface provides for converting data from any of the Java primitive types to a series of bytes and writing these bytes to a binary stream. There is also a facility for converting a String into modified UTF-8 format and writing the resulting series of bytes.

For all the methods in this interface that write bytes, it is generally true that if a byte cannot be written for any reason, an IOException is thrown.

主要是提供了将Java原生类型转为bytes的接口。

但是也只能转Java原生类型,想转自定义/复杂对象需要自己去实现一些东西,比如实现Writable之类的。

对JavaIO的小小整理

这里写图片描述

猜你喜欢

转载自blog.csdn.net/puppylpg/article/details/80018586
今日推荐