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;