走进JDK(四)------InputStream、OutputStream、Reader、Writer

InputStream

InputStream是java中的输入流,下面基于java8来分析下InputStream源码

一、类定义

public abstract class InputStream implements Closeable

Closeable接口定义了close()方法,流在使用完之后需要关闭,并且放在finally块中操作比较好。

二、变量

// 该变量用于确定在skip方法中使用的最大缓存数组大小。
private static final int MAX_SKIP_BUFFER_SIZE = 2048;

三、主要方法

1、read()

//从输入流中读取下一字节数据。返回的字节值为一个范围在0-255之间的int数。若由于到达流的尾部而没有字节可获取,则返回-1.直到数据可达,检测到流的末尾或者抛出一个异常,该方法才停止。由每个子类自己实现。
public abstract int read() throws IOException;
//将数据放入到字节数组中,内部调用的也是read(b, 0, b.length)
public int read(byte b[]) throws IOException {
    return read(b, 0, b.length);
}
//byte[] b代表存放数据的字节数组,off代表将读取的数据写入数组b的起始偏移地址。len默认则是b的长度
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;
        }

//这地方有个很有意思的问题,为啥不用byte作为接收值,需要int,此方法的返回值的范围为0-255,如果使用byte,则byte会将255转成-1,这样就会与read()方法的约定混淆,因为返回-1时,会认为流中已经没有数据,但此时是有数据的。
//那么java内部byte是怎么转成int的呢?例如对于-1这个值,在java中,负数都是以补码的形式存在(可以参考本人另外一篇文章)。-1的补码就是11111111,因为byte是1个字节8位。而int是4个字节32位,当byte->int时,所有的高位都会补0,
//-1对应的int则为00000000 00000000 00000000 11111111,这个数在int中则为255,也就是说byte的-1转成就是int的255.所以在下面(byte)c就可以将数据还原成byte,这种做法既可以保证读取到数据为128-255时不会出错,也能保证byte->int->byte
//读取的是原始数据
        int c = read();
        if (c == -1) {
            return -1;
        }
        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;
}

 2、available()

可以在读写操作前先得知数据流里有多少个字节可以读取。需要注意的是,如果这个方法用在从本地文件读取数据时,一般不会遇到问题,但如果是用于网络操作,就经常会遇到一些麻烦。比如,Socket通讯时,对方明明发来了1000个字节,
但是自己的程序调用available()方法却只得到900,或者100,甚至是0,感觉有点莫名其妙,怎么也找不到原因。其实,这是因为网络通讯往往是间断性的,一串字节往往分几批进行发送。
本地程序调用available()方法有时得到0,这可能是对方还没有响应,也可能是对方已经响应了,但是数据还没有送达本地。对方发送了1000个字节给你,也许分成3批到达,这你就要调用3次available()方法才能将数据总数全部得到。
InputStream中的此方法一直返回的0,能否使用取决于实现了InputStream这个抽象类的具体子类中有没有实现available这个方法。

public int available() throws IOException {
        return 0;
}

很多小伙伴在读取流之前喜欢使用available()方法来判断有多少字节,写法如下:

int count = in.available(); 
byte[] b = new byte[count]; 
in.read(b);

这样在网络延迟的情况下就会有问题,应改成如下这种(不过这种也有问题,当流没数据一直循环):

int count = 0; 
while (count == 0) { 
    count = in.available(); 
} 
byte[] b = new byte[count]; 
in.read(b);

3、close()

//关闭输入流并释放与其相关的系统资源。一般放在finally中操作
public void close() throws IOException {}

OutputStream

 一、类定义

public abstract class OutputStream implements Closeable, Flushable

flushable接口则定义flush()

二、主要方法

1、write()

//将指定的一个字节写入此输出流。int 值为 4 个字节,此方法丢弃 int 类型高位的 3 个字节,只保留低位的 1 个字节写入(对字节来说,转为 int 其 3 个高位字节都是全 0 的,所以可以丢弃)。此方法是抽象方法,子类必须要进行实现
public abstract void write(int b) throws IOException;
//将 b.length 个字节从指定的 byte 数组写入此输出流。调用下边的 write 方法。
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]);
        }
}

2、flush()

//刷新此输出流并强制写出所有缓冲的输出字节。此类未实现具体行为,子类应该复写此方法。
public void flush() throws IOException {}

3、close()

//关闭此输出流并释放与此流有关的所有系统资源,此类未实现具体行为,子类应该复写此方法。
public void close() throws IOException {}

Reader

一、类定义

public abstract class Reader implements Readable, Closeable

Readable:Readable 接口表示尝试将字符读取到指定的缓冲中。

Closeable:Closeable 接口表示 Reader 可以被close。

二、变量

//最大跳过缓冲的大小
private static final int maxSkipBufferSize = 8192;
//是一个 char[] 类型,表示跳过缓冲
private char skipBuffer[] = null;
//是 Reader 的锁,用于实现同步
protected Object lock;

三、构造函数

protected Reader() {
    this.lock = this;
}
protected Reader(Object lock) {
    if (lock == null) {
        throw new NullPointerException();
    }
    this.lock = lock;
}

四、主要方法

1、read()

//所有 read 方法最终都会调用这个抽象方法,提供给子类处理逻辑的实现。它传入的三个参数,字符数组cbuf、偏移量off和数组长度。
public abstract int read(char cbuf[], int off, int len) throws IOException;
//无参的 read 方法其实是默认读一个字符,new 一个 char 对象然后调用子类实现进行读取,最后返回读到的字符
public int read() throws IOException {
    char cb[] = new char[1];
    if (read(cb, 0, 1) == -1)
        return -1;
    else
        return cb[0];
}
//假如 read 方法传入的参数为 char 数组时,则直接调用子类实现进行读取
public int read(char cbuf[]) throws IOException {
    return read(cbuf, 0, cbuf.length);
}
//最后一个 read 方法其实是 Readable 接口定义的方法,用于读取字符到指定的 CharBuffer 对象中,逻辑是先得到 CharBuffer 对象剩余长度,根据该长度实例化 char 数组,然后调用子类实现完成读取,最后将读取到的字符放进 CharBuffer 对象
public int read(java.nio.CharBuffer target) throws IOException {
    int len = target.remaining();
    char[] cbuf = new char[len];
    int n = read(cbuf, 0, len);
    if (n > 0)
        target.put(cbuf, 0, n);
    return n;
}

2、close()

abstract public void close() throws IOException;

Writer

 一、类定义

public abstract class Writer implements Appendable, Closeable, Flushable

二、变量

//字符缓存数组。用于临时存放要写入字符输出流中的字符
private char[] writeBuffer;
//字符缓存数组的默认大小
private static final int WRITE_BUFFER_SIZE = 1024;
//用于同步针对此流的操作的对象
protected Object lock;

三、构造函数

protected Writer() {
    this.lock = this;
}
protected Writer(Object lock) {
    if (lock == null) {
        throw new NullPointerException();
    }
    this.lock = lock;
}

四、主要方法

1、write()

//写入单个字符。要写入的字符包含在给定整数值的16个低位中,16高位被忽略。
    public void write(int c) throws IOException {
        synchronized (lock) {
            if (writeBuffer == null){
                writeBuffer = new char[WRITE_BUFFER_SIZE];
            }
            writeBuffer[0] = (char) c;
            write(writeBuffer, 0, 1);
        }
    }
//将一个字符数组写入到writerBuffer中
    public void write(char cbuf[]) throws IOException {
        write(cbuf, 0, cbuf.length);
    }
//试图将字符数组中从off开始的len个字符写入输出流中。尽量写入len个字符,但写入的字节数可能少于len个,也可能为零。
abstract public void write(char cbuf[], int off, int len) throws IOException;
//写入字符串
public void write(String str) throws IOException {
        write(str, 0, str.length());
    }
//试图将字符串中从off开始的len个字符写入输出流中。尽量写入len个字符,但写入的字节数可能少于len个,也可能为零。
public void write(String str, int off, int len) throws IOException {
        synchronized (lock) {
            char cbuf[];
            if (len <= WRITE_BUFFER_SIZE) {
                if (writeBuffer == null) {
                    writeBuffer = new char[WRITE_BUFFER_SIZE];
                }
                cbuf = writeBuffer;
            } else {    // Don't permanently allocate very large buffers.
                cbuf = new char[len];
            }
            str.getChars(off, (off + len), cbuf, 0);
            write(cbuf, 0, len);
        }
    }

2、append()

    /**
     * 添加字符序列
     */
    public Writer append(CharSequence csq) throws IOException {
        if (csq == null)
            write("null");
        else
            write(csq.toString());
        return this;
    }

    /**
     * 添加字符序列的一部分
     */
    public Writer append(CharSequence csq, int start, int end) throws IOException {
        CharSequence cs = (csq == null ? "null" : csq);
        write(cs.subSequence(start, end).toString());
        return this;
    }

    /**
     * 添加指定字符
     */
    public Writer append(char c) throws IOException {
        write(c);
        return this;
    }

3、flush()

    /**
     * 刷新该流的缓冲。
     */
    abstract public void flush() throws IOException;

4、close()

    /**
     * 关闭此流,但要先刷新它。
     */
    abstract public void close() throws IOException;

猜你喜欢

转载自www.cnblogs.com/alimayun/p/10693177.html
今日推荐