【原创】从源码剖析IO流(一)输入流与输出流--转载请注明出处

InputStream与OutPutStream两个抽象类,是所有的流的基础,首先来看这两个流的API

InputStream:

public abstract int read() throws IOException; 从输入流中读取数据的下个字节。
public int read(byte b[]) throws IOException{…} 从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中。
public int read(byte b[], int off, int len) throws IOException{…} 将输入流中最多len个数据字节读入byte数组。
public long skip(long n) throws IOException{…} 跳过和丢弃此输入流中数据的n个字节。
public int available() throws IOException{…} 返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数。
public void close() throws IOException {} 关闭此输入流并释放与该流关联的所有系统资源。
public synchronized void mark(int readlimit) {} 在此输入流中标记当前的位置。对reset方法的后续调用会在最后标记的位置重新定位此流,以便后续读取重新读取相同的字节。
public synchronized void reset() throws IOException {…} 将此流重新定位到最后一次对此输入流调用mark方法时的位置。
public boolean markSupported() {…} 测试此输入流是否支持mark和reset方法。

OutPutStream

方法 说明
public abstract void write(int b) throws IOException; 将指定的字节写入此输出流。
public void write(byte b[]) throws IOException {…} 将b.length个字节从指定的byte数组写入此输出流。
public void write(byte b[], int off, int len) throws IOException {…} 将指定byte数组中从偏移量off开始的len个字节写入此输出流。
public void flush() throws IOException {} 刷新此输出流并强制写出所有缓冲的输出字节。
public void close() throws IOException {} 关闭此输出流并释放与此流有关的所有系统资源。

这两个类的方法如上,可以看到在这两个方法中,分别具有一个抽象方法,分别为read()和write()方法。这两个方法便是整个IO体系中的核心方法,所有的内容均由这两个方法为基础进行实现。然后我们来看一下主要的两个比较主要的read(byte b[], int off, int len) 方法和write(byte b[], int off, int len)方法的实现:

/**
 * 将输入流中最多len个字节读入byte数组。尝试读取len个字节,但读取的字节也可能小于该值。以整数形式返回实际读取的字节数。
 * 
 * 在输入数据可用、检测到流末尾或者抛出异常前,此方法一直阻塞。
 * 
 * 如果len为0,则不读取任何字节并返回0;否则,尝试读取至少一个字节。如果因为流位于文件末尾而没有可用的字节,则返回值-1;否则,至少读取一个字节并将其存储在b中。
 *
 * 将读取的第一个字节存储在元素b[off]中,下一个存储在 b[off+1] 中,依次类推。读取的字节数最多等于len。设k为实际读取的字节数;这些字节将存储在b[off]到b[off+k-1]的元素中,不影响b[off+k]到b[off+len-1]的元素。
 * 
 * 在任何情况下,b[0]到b[off]的元素以及b[off+len]到b[b.length-1] 的元素都不会受到影响。
 * 
 * 类InputStream的read(b, off, len)方法重复调用方法read()。如果第一次这样的调用导致IOException,则从对read(b, off, len)方法的调用中返回该异常。如果对read()的任何后续调用导致IOException,则捕获该异常并将其视为到达文件末尾;到达该点时读取的字节存储在b中,并返回发生异常之前读取的字节数。在已读取输入数据len的请求数量、检测到文件结束标记、抛出异常前,此方法的默认实现将一直阻塞。建议子类提供此方法更为有效的实现。
 * 
 * @param      b     读入数据的缓冲区。
 * @param      off   数组 b 中将写入数据的初始偏移量。
 * @param      len   要读取的最大字节数。
 * @return     读入缓冲区的总字节数;如果因为已到达流末尾而不再有数据可用,则返回-1。
 * @exception  IOException 如果不是因为位于文件末尾而无法读取第一个字节;如果输入流已关闭;如果发生其他 I/O 错误。
 * @exception  NullPointerException  如果 b 为 null。
 * @exception  IndexOutOfBoundsException 如果off为负,len为负,或者len大于b.length - off
 * @see        java.io.InputStream#read()
 */
public int read(byte b[], int off, int len) throws IOException {
    //检查参数是否合法,不合法抛出异常
    if (b == null) {
        throw new NullPointerException();
    } else if (off < 0 || len < 0 || len > b.length - off) {
        throw new IndexOutOfBoundsException();
    } else if (len == 0) {
        return 0;
    }
    // 读取一个字节
    int c = read();
    // 如果因为流位于文件末尾而没有可用的字节,返回-1
    if (c == -1) {
        return -1;
    }
    // 将读取的第一个字节存储在元素b[off]中
    b[off] = (byte)c;

    int i = 1;
    // 继续往下读取
    try {
        for (; i < len ; i++) {
            c = read();
            if (c == -1) {
                break;
            }
            b[off + i] = (byte)c;
        }
    } catch (IOException ee) {
    }
    return i;
}
/**
 * 将指定byte数组中从偏移量off开始的len个字节写入此输出流。
 * 
 * write(b, off, len) 的常规协定是:
 * 将数组b中的某些字节按顺序写入输出流;
 * 元素b[off]是此操作写入的第一个字节,b[off+len-1]是此操作写入的最后一个字节。
 * 
 * OutputStream 的write方法对每个要写出的字节调用一个参数的write 方法。
 * 建议子类重写此方法并提供更有效的实现。
 *
 * @param      b     数据。
 * @param      off   数据中的初始偏移量。
 * @param      len   写入的字节数。
 * @exception  IOException   如果发生I/O错误。尤其是关闭了输出流。
 */
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;
    }
    // 将指定byte数组中从偏移量off开始的len个字节写入此输出流。
    for (int i = 0 ; i < len ; i++) {
        write(b[off + i]);
    }
}

从上方两个代码片段中,可以看出在进行读取流时,操作时非常简单的,只是调用write方法与read方法进行处理流信息。分别为将read出来的字节,写入到byte[]的某位中,将一个byte[]字节中的某位,写入到流中。

此时,我们再看一段比较有意思的代码,这段代码是InputStream中的skip()方法:

/**
 * 跳过和丢弃此输入流中数据的n个字节。出于各种原因,skip方法结束时跳过的字节数可能小于该数,也可能为0。导致这种情况的原因很多,跳过n个字节之前已到达文件末尾只是其中一种可能。返回跳过的实际字节数。如果n为负,方法返回0,,不跳过任何字节。子类可能对负值有不一样的处理。
 *
 * skip方法创建一个byte数组,然后重复将字节读入其中,直到读够n个字节或已到达流末尾为止。建议子类提供此方法更为有效的实现。例如,可依赖搜索能力的实现。
 *
 * @param      n   要跳过的字节数。
 * @return     实际跳过的字节数
 * @exception  IOException  如果流不支持搜索,或者发生其他 I/O 错误。
 */
public long skip(long n) throws IOException {

    long remaining = n;
    int nr;
    // 如果n<=0,返回0
    if (n <= 0) {
        return 0;
    }

    int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
    // 创建一个byte数组,然后重复将字节读入其中,直到读够n个字节或已到达流末尾为止。
    byte[] skipBuffer = new byte[size];
    while (remaining > 0) {
        nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
        if (nr < 0) {
            break;
        }
        remaining -= nr;
    }

    return n - remaining;
}

这个方法的用途为,将接下来的字节丢弃,在这个方法中,进行的操作为新创建一个空的byte[],然后将这N个字节的信息,读取到这个byte[]中,然后将这个byte[]弃之一边,等待GC进行回收。

然后,我们要注意一下的是,默认InputStream与OutPutStream的源码中,close()方法的实现都是空的,包括输出流中的close()方法。

总结:在InputStream与OutPutStream中,为我们提供了一些基本的write和read的方法的封装,但是这些方法都是依赖于抽象的read()方法与write()方法的。同时,还可以注意到,在进行InputStream的读取时,如果想要跳过若干个字节,这里的处理实际上是对这若干个字节进行了读取,只是没有返回给上层方法,没有对允许这部分的字节进行任何的操作,直接是在等待GC进行回收了。

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