通过JDK源码学习InputStream详解
概况
本文主要给大家介绍了通过jdk源码学习inputstream的相关内容,jdk 给我们提供了很多实用的输入流 xxxinputstream,而 inputstream 是所有字节输入流的抽象。包括 bytearrayinputstream 、filterinputstream 、bufferedinputstream 、datainputstream 和 pushbackinputstream 等等。下面话不多说了,来一起看看详细的介绍吧。
如何阅读jdk源码。
以看核心虚拟机(hotspot)code为例介绍。
1)熟悉虚拟机原理。调bug可以不懂原理,但是看code必须懂原理,从code里面看原理,基本不可能。hotspot的code写的挺乱的,想直接通过code以及code中的注释看明白还是很困难的。所以先熟悉虚拟机的原理,再去看code,会针对性比较强。
2)分模块阅读code。hotspot包括的模块确实太多,我们需要分成不同的模块各个击破。以gc为例,hotspot中的gc算法有很多种,parallel scavenge,cms,g1…等等,先弄懂这些算法的原理,再去看code会比较快。不要看二手资料,不要看翻译资料,推荐r大的hllvm论坛以及周志明的深入java虚拟机,hotspot源码阅读这本书写的也还可以。
继承结构
--java.lang.object --java.io.inputstream
类定义
public abstract class inputstream implements closeable
inputstream 被定为 public 且 abstract 的类,实现了closeable接口。
closeable 接口表示 inputstream 可以被close,接口定义如下:
public interface closeable extends autocloseable { public void close() throws ioexception; }
主要属性
private static final int max_skip_buffer_size = 2048; private static final int default_buffer_size = 8192; private static final int max_buffer_size = integer.max_value - 8;
- max_skip_buffer_size 表示输入流每次最多能跳过的字节数。
- default_buffer_size 默认的缓冲大小。
- max_buffer_size 表示最大的缓冲数组大小,这里设置为 integer.max_value - 8 这里也是考虑到 jvm 能支持的大小,超过这个值就会导致 outofmemoryerror。
主要方法
read方法
一共有三个 read 方法,其中有一个抽象的 read 方法,其余两个 read 方法都会调用这个抽象方法,该方法用于从输入流读取下一个字节,返回一个0到255范围的值。如果已经到达输入流结尾处而导致无可读字节则返回-1,同时,此方法为阻塞方法,解除阻塞的条件:
1. 有可读的字节。
2. 检测到已经是输入流的结尾了。
3. 抛出异常。
主要看第三个 read 方法即可,它传入的三个参数,byte数组、偏移量和数组长度。该方法主要是从输入流中读取指定长度的字节数据到字节数组中,需要注意的是这里只是尝试去读取长度为 len 的数组,但真正读取到的数组长度不一定为 len,返回值才是真正读取到的长度。
public abstract int read() throws ioexception; public int read(byte b[]) throws ioexception { return read(b, 0, b.length); } 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(); 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; }
看看它的逻辑,数组为null则抛空指针,偏移量和长度超过边界也抛异常,长度为0则什么都不敢直接返回0。接着调用 read() 读取一个字节,如果为-1则说明结束,直接返回-1。否则继续根据数组长度循环调用 read() 方法读取字节,并且填充到传入的数组对象中,最后返回读取的字节数。
readallbytes方法
该方法从输入流读取所有剩余的字节,在此过程是阻塞的,直到所有剩余字节都被读取或到达流的结尾或发生异常。
逻辑是用一个 for 循环内嵌一个 while 循环,while 循环不断调用 read 方法尝试将 default_buffer_size 长度的字节数组填满,一旦填满则需要将数组容量扩容一倍,再将原字节数组复制到新数组中,然后再通过 while 循环继续读取,直到达到尾部才跳出 for 循环,最后返回读取到的所有字节数组。
public byte[] readallbytes() throws ioexception { byte[] buf = new byte[default_buffer_size]; int capacity = buf.length; int nread = 0; int n; for (;;) { while ((n = read(buf, nread, capacity - nread)) > 0) nread += n; if (n < 0) break; if (capacity <= max_buffer_size - capacity) { capacity = capacity << 1; } else { if (capacity == max_buffer_size) throw new outofmemoryerror("required array size too large"); capacity = max_buffer_size; } buf = arrays.copyof(buf, capacity); } return (capacity == nread) ? buf : arrays.copyof(buf, nread); }
readnbytes方法
从输入流中读取指定长度的字节,而且它能保证一定能读取到指定的长度,它属于阻塞方式,用一个 while 循环不断调用 read 读取字节,直到读取到指定长度才结束读取。
public int readnbytes(byte[] b, int off, int len) throws ioexception { objects.requirenonnull(b); if (off < 0 || len < 0 || len > b.length - off) throw new indexoutofboundsexception(); int n = 0; while (n < len) { int count = read(b, off + n, len - n); if (count < 0) break; n += count; } return n; }
available方法
返回从该输入流能进行非阻塞读取的剩余字节数,当调用 read 读取的字节数一般会小于该值,有一些inputstream的子实现类会通过该方法返回流的剩余总字节数,但有些并不会,所以使用时要注意点。
这里抽象类直接返回0,子类中重写该方法。
public int available() throws ioexception { return 0; }
skip方法
从输入流中跳过指定个数字节,返回值为真正跳过的个数。这里的实现是简单通过不断调用 read 方法来实现跳过逻辑,但这是较低效的,子类可用更高效的方式重写此方法。
下面看看逻辑,最大的跳过长度不能超过 max_skip_buffer_size ,并且用一个 while 循环调用 read 方法,如果遇到返回为-1,即已经到达结尾了,则跳出循环。可以看到 skipbuffer 其实是没有什么作用,直接让其被 gc 即可,最后返回真正跳过的字节数。
public long skip(long n) throws ioexception { long remaining = n; int nr; if (n <= 0) { return 0; } int size = (int)math.min(max_skip_buffer_size, remaining); 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; }
close方法
此方法用于关闭输入流,并且释放相关资源 。
public void close() throws ioexception {}
transferto方法
从输入流中按顺序读取全部字节并且写入到指定的输出流中,返回值为转移的字节数。转移过程中可能会发生不确定次的阻塞,阻塞可能发生在 read 操作或 write 操作。
主要逻辑是用 while 循环不断调用 read 方法操作读取字节,然后调用输出流的 write 方法写入,直到读取返回-1,即达到结尾。最后返回转移的字节数。
public long transferto(outputstream out) throws ioexception { objects.requirenonnull(out, "out"); long transferred = 0; byte[] buffer = new byte[default_buffer_size]; int read; while ((read = this.read(buffer, 0, default_buffer_size)) >= 0) { out.write(buffer, 0, read); transferred += read; } return transferred; }
marksupported方法
是否支持 mark 和 reset 操作,这里直接返回 false,子类根据实际重写该方法。
public boolean marksupported() { return false; }
mark方法
标记输入流当前位置,与之对应的是 reset 方法,通过他们之间的组合能实现重复读取操作。另外它会传入 readlimit 参数,它用于表示该输入流中在执行 mark 操作后最多可以读 readlimit 个字节后才使 mark 的位置失效。
可以看到 inputstream 的 mark 方法是什么都不做的,子类中再具体实现。
public synchronized void mark(int readlimit) {}
reset方法
与 mark 方法对应,它可以重置输入流的位置到上次被 mark 操作标识的位置。inputstream 的 reset 方法直接抛出一个 ioexception,子类中根据实际情况实现。
public synchronized void reset() throws ioexception { throw new ioexception("mark/reset not supported"); }
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。