通过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算法有很多种,parallelscavenge,cms,g1…等等,先弄懂这些算法的原理,再去看code会比较快。不要看二手资料,不要看翻译资料,推荐R大的hllvm论坛以及周志明的深入java虚拟机,hotspot源码阅读这本书写的也还可以。
继承结构
--java.lang.Object --java.io.InputStream
类定义
publicabstractclassInputStreamimplementsCloseable
InputStream被定为public且abstract的类,实现了Closeable接口。
Closeable接口表示InputStream可以被close,接口定义如下:
publicinterfaceCloseableextendsAutoCloseable{ publicvoidclose()throwsIOException; }
主要属性
privatestaticfinalintMAX_SKIP_BUFFER_SIZE=2048; privatestaticfinalintDEFAULT_BUFFER_SIZE=8192; privatestaticfinalintMAX_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,返回值才是真正读取到的长度。
publicabstractintread()throwsIOException; publicintread(byteb[])throwsIOException{ returnread(b,0,b.length); } publicintread(byteb[],intoff,intlen)throwsIOException{ if(b==null){ thrownewNullPointerException(); }elseif(off<0||len<0||len>b.length-off){ thrownewIndexOutOfBoundsException(); }elseif(len==0){ return0; } intc=read(); if(c==-1){ return-1; } b[off]=(byte)c; inti=1; try{ for(;i看看它的逻辑,数组为null则抛空指针,偏移量和长度超过边界也抛异常,长度为0则什么都不敢直接返回0。接着调用read()读取一个字节,如果为-1则说明结束,直接返回-1。否则继续根据数组长度循环调用read()方法读取字节,并且填充到传入的数组对象中,最后返回读取的字节数。
readAllBytes方法
该方法从输入流读取所有剩余的字节,在此过程是阻塞的,直到所有剩余字节都被读取或到达流的结尾或发生异常。
逻辑是用一个for循环内嵌一个while循环,while循环不断调用read方法尝试将DEFAULT_BUFFER_SIZE长度的字节数组填满,一旦填满则需要将数组容量扩容一倍,再将原字节数组复制到新数组中,然后再通过while循环继续读取,直到达到尾部才跳出for循环,最后返回读取到的所有字节数组。
publicbyte[]readAllBytes()throwsIOException{ byte[]buf=newbyte[DEFAULT_BUFFER_SIZE]; intcapacity=buf.length; intnread=0; intn; 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) thrownewOutOfMemoryError("Requiredarraysizetoolarge"); capacity=MAX_BUFFER_SIZE; } buf=Arrays.copyOf(buf,capacity); } return(capacity==nread)?buf:Arrays.copyOf(buf,nread); }readNBytes方法
从输入流中读取指定长度的字节,而且它能保证一定能读取到指定的长度,它属于阻塞方式,用一个while循环不断调用read读取字节,直到读取到指定长度才结束读取。
publicintreadNBytes(byte[]b,intoff,intlen)throwsIOException{ Objects.requireNonNull(b); if(off<0||len<0||len>b.length-off) thrownewIndexOutOfBoundsException(); intn=0; while(navailable方法
返回从该输入流能进行非阻塞读取的剩余字节数,当调用read读取的字节数一般会小于该值,有一些InputStream的子实现类会通过该方法返回流的剩余总字节数,但有些并不会,所以使用时要注意点。
这里抽象类直接返回0,子类中重写该方法。
publicintavailable()throwsIOException{ return0; }skip方法
从输入流中跳过指定个数字节,返回值为真正跳过的个数。这里的实现是简单通过不断调用read方法来实现跳过逻辑,但这是较低效的,子类可用更高效的方式重写此方法。
下面看看逻辑,最大的跳过长度不能超过MAX_SKIP_BUFFER_SIZE,并且用一个while循环调用read方法,如果遇到返回为-1,即已经到达结尾了,则跳出循环。可以看到skipBuffer其实是没有什么作用,直接让其被GC即可,最后返回真正跳过的字节数。
publiclongskip(longn)throwsIOException{ longremaining=n; intnr; if(n<=0){ return0; } intsize=(int)Math.min(MAX_SKIP_BUFFER_SIZE,remaining); byte[]skipBuffer=newbyte[size]; while(remaining>0){ nr=read(skipBuffer,0,(int)Math.min(size,remaining)); if(nr<0){ break; } remaining-=nr; } returnn-remaining; }close方法
此方法用于关闭输入流,并且释放相关资源。
publicvoidclose()throwsIOException{}transferTo方法
从输入流中按顺序读取全部字节并且写入到指定的输出流中,返回值为转移的字节数。转移过程中可能会发生不确定次的阻塞,阻塞可能发生在read操作或write操作。
主要逻辑是用while循环不断调用read方法操作读取字节,然后调用输出流的write方法写入,直到读取返回-1,即达到结尾。最后返回转移的字节数。
publiclongtransferTo(OutputStreamout)throwsIOException{ Objects.requireNonNull(out,"out"); longtransferred=0; byte[]buffer=newbyte[DEFAULT_BUFFER_SIZE]; intread; while((read=this.read(buffer,0,DEFAULT_BUFFER_SIZE))>=0){ out.write(buffer,0,read); transferred+=read; } returntransferred; }markSupported方法
是否支持mark和reset操作,这里直接返回false,子类根据实际重写该方法。
publicbooleanmarkSupported(){ returnfalse; }mark方法
标记输入流当前位置,与之对应的是reset方法,通过他们之间的组合能实现重复读取操作。另外它会传入readlimit参数,它用于表示该输入流中在执行mark操作后最多可以读readlimit个字节后才使mark的位置失效。
可以看到InputStream的mark方法是什么都不做的,子类中再具体实现。
publicsynchronizedvoidmark(intreadlimit){}reset方法
与mark方法对应,它可以重置输入流的位置到上次被mark操作标识的位置。InputStream的reset方法直接抛出一个IOException,子类中根据实际情况实现。
publicsynchronizedvoidreset()throwsIOException{ thrownewIOException("mark/resetnotsupported"); }总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。