java源码之 io 流源码解读(一)

    刚刚喝了一波毒鸡汤,其中印象最深的就是这两个:

            没有人能够让你放弃梦想,自己想想就放弃了。

            找对象的时候不能光看对方的外表。。。。  还要看看自己的外表    哈哈哈~~

    吸收了这一大波精气之后,我感觉我的精力值已经足够支撑我将这篇文章完成,嘿嘿   走你…\

    花了一周的零碎时间,整理了一下相关的类关系,虽然网上有很多,但是自己动手做一遍效果要好不知道多少。但是不知道怎么传大图,只能将它调的很小穿上来。   嗯,上图吧:

        《java源码之 io 流源码解读(一)》

    咋一看,要比集合体系的图复杂呢。。  哎,也正常,毕竟这要更高层一点吧,不在同一个位面嘿嘿嘿。 之前上课用的也好,自己下来练习的也好,总是将这些什么流啊之类的搞混,整的自己晕头转向的,好不容易记住的大致的用法,过个十天半个月不用又是一脸懵逼了。。。。   于是狠下心来将其源码粗略看看,但是尽管这样自己已经收获到了很多。

    继续说说流吧!    流,流量,水流,其英文单词为Stream,我自然在idea上面去stream 查看一下呢,果不其然,我找到了。经过几分钟的观察惊讶的发现它竟然是java.util包下的。   哎呀妈呀这尴尬大了。   寻思还是跟着大牛的脚步走吧,免得自己又乱来嘿嘿嘿。  于是:以这张图为索引:

        《java源码之 io 流源码解读(一)》

    不得不说这下顺了很多,但是我向来不喜欢被牵着鼻子走,遂还是从接口至下,慢慢专研,所谓心急吃不了热豆腐(但是这速度已经很快了,强行吃热豆腐肯定是要被烫嘴的嘻嘻)。   于是,通过源码很快的可以发现,io体系的顶层接口为  autoclosable,其方法接口很简单,只有一个close()。   但是通过后面的调试我大致能理解到,实现了autoclosable接口的类最后会交给 finilize管理,也就是jvm的垃圾收集器,至于它主要功能是什么,只可意会不好言传哈哈哈。  而往往类要调用的时候并不是直接实现autoclosable,而是实现其子接口closable,可能也是为了多态吧。。。    

    因此,从第一张关系图中,可以看到:  io体系里面所有的抽象基类:   InputStream,OutputStream,Reader,Writer都实现了该接口。   想表达的太多竟然一时间不知道该从哪个开始表述,那就按照自己观看源码的时间线来说吧。

    以前就有所猜测,流的本质是什么?  事实上并不存在流的类,有一个流的接口但是是作为java.util工具包下的接口,既然是作为一个接口,功能的抽象那么问其本质就牛头不对马嘴了。   看来自己以后应该思考的要换成:  输入流的本质是什么?它的原理是什么?  和  输出流的本质是什么?它的原理是什么了吧。  尽管如此,原来猜测的答案便是:   数组。  (这是作为一种逻辑结构而言,若从物理结构而言, 那所有的东西都可以称之为数组了吧,毕竟是线性的,当然这是在不考虑操作方式的情况下。不知道理解的对不对,管他的,有自己的理解我都是看成是经验吧。)。  这一次的练习算是进一步加深了自己的理解吧,也算验证了自己的猜测。  那就是 byte数组。

    万法归宗,万本归源。  对于InputStream抽象基类而言,或者说成是输入流而言,其最最最最最核心的莫过于 read()方法了,对于输入类的基本流也好,包装流也好,可以说它们都是在这上面做文章了,只是说呢,这文章做的逼格高了点,让我等普通人不敢望其项背。  本尊面目如下:

        《java源码之 io 流源码解读(一)》

    我第一个开刀的自然是用的最多,让我痛苦不堪的  FileInputStram了。   不过整体而言它算中等复杂的一个类吧。(因为不复杂有点骄傲,那就中等复杂吧!)。 既然是叫做文件输入流,那肯定是跟文件有关了,自然免不了要去看看File类。 之前朋友遇到过这样一种情况,他希望在系统的文件系统中建一个文件,于是他采用了该方法。  new File(“D:\\dev\\test.txt”)。  运行后去文件系统查看的时候竟然没有该文件。  这也引起了我的思考,为什么会没有呢?  它们之间通过什么联系在一起的呢?   没想到一直拖到了现在才想起来去探究这个问题。  不过还好,不算太晚。      其满足主要的一些依赖:

            《java源码之 io 流源码解读(一)》

     可以看到FileInputStream依赖于两个类,直接相关的是File,其又依赖于FileSystem,从名字也可以分析出这是跟文件系统有关的抽象类,因为我是windows系统,所以它的实现类为  WinNTFileSystem,通过查看源码,可以看到其中有多个native方法,也就是归c管的,或者说归系统管吧。

    既然类结构了解了,那就继续来处理那个多年的疑问吧?  既然new File(“…”)不能创建文件,那么怎么创建文件呢?File类的边界在哪?  它是以何种时机保存我们所设定的信息呢?   嗯差不多就这些问题,但是当时好像没有截图,就口头表述吧。   事实上我们实例化一个文件,也就是  new File()的时候,事实上并没有与系统交互,其实例化后主要做了如下两件事情:  1.规范化路径名,也就是将分隔符换成本系统支持的分隔符;   2,判断该路径的类型,是完全绝对路径呢?  还是相对项目根路径的绝对路径呢?  还是完全的相对路径呢?  所以当我们实例化之后没有文件生成也就能理解了,那么当我们调用实例化后的当前对象的createNewFile()之后,为什么又有文件呢? 它做了哪些事情呢?  请看图:

        《java源码之 io 流源码解读(一)》

        《java源码之 io 流源码解读(一)》

        嘿嘿,是不是感觉有一种柳暗花明又一村的感觉哈哈哈。但是至少这里我算粗鲁的处理了我的疑问。

        上面那个已经超出了io流体系,算是一个延伸吧。。  继续回过头来,作为一个具体的输入流,自然是要看它的最核心的方法的:

        《java源码之 io 流源码解读(一)》

            至于其他的方法,多字节读取啊,按字符读取啊,按字符数组读取啊,种种,原来最终其实都是执行的这个,真扯淡,原来以为挺神秘的。

            关于多字节读取需要说明的一点就是:  多字节读取为过程调用,使用方式上面,跟单字节读取有一些差别。单字节读取为函数调用,其结果通过返回值呈现。  而多字节读取为过程调用,其返回值虽然也为int但是意义变了,目标参数为复合数据类型 数组,因此结果的呈现直接去我们定义的目标byte[]中取。这也是我长久来的一个疑惑吧。  但是本质上,都是read()负责读取,并且是单字节读取。方法如下:

            《java源码之 io 流源码解读(一)》

            还有一个挺重要的就是,FileInputStream何种时机与系统交互?

        《java源码之 io 流源码解读(一)》

        《java源码之 io 流源码解读(一)》

        嘿嘿,它在实例化输入流的时候就交互了,就是这么暴力。  当然前提是你以文件去实例化。

        对啦对啦,还有一个特别重要的话题!!  那就是既然read0()方法是外部的,并且是一个函数调用,也就是每次只需读取需要的那个byte,而对读取状态,读取位置的推进,读取安全性等都不是java去维护,那么谁对这个线程负责,谁来管理维护呢?  总的有人管理吧,不然早乱了套了。    具体是谁我也不是很清楚,但是我猜想有几种可能性:   1.协议栈去维护,比如tcp协议,它就是一个基于连接的可靠的传输机制,其通信过程中本身就记录了状态信息,位置信息等。    2.c函数去维护,即通过jni调用的c函数,其本质也是一个高级程序管理的。(最近看的一些书,让我对所谓的高级语言,低级语言有了一些特别的认识。  比如asm(汇编语言)与c(c语言),可以说是作为高级与低级语言的典型代表了。  但是其实asm里面也是那些跳转语句,移位语句啊之类的,只是它在表达方式让对人来说不易理解,并且写起来很麻烦。而c则更易读,实际上(自己的理解不知道对否)是对asm的一个包装。这就让我对软硬件更加的着迷了,奈何自己要搞钱,不然肯定去深深专研去了。)。  不管怎么样吧,反正跟java 内部无关,有一句话是这样说的,程序在没有被调用的时候在硬盘中,被调用的时候就从内存中复制到内存中运行,也就是运行的程序都是在内存中的。  从这个意义上讲,这个FileInputStream是从外部读取东西。   到这里,基本算是我对这个输入流的所有的疑惑与感悟了。

    万事开头难。  既然处理了一个输入流了,那么接下来的应该都好办了。    第二个是最复杂的一个,类最多的一个,但同时它也是最懒的。   为啥呢??  从它的名字可以看出来:   FilterInputStream,顾名思义,过滤输入流,哈哈哈翻译的很生硬。但是它确实类比较多,也很复杂。  你看:  BufferedInputStream,DataInputStream,还有一个跟这个有一点血缘关系的  最复杂的 ObjectInputStram(似曾相识啊,要么在观看大牛解析tomcat的时候好像看到过它,要不就是在debug springboot的时候看到过它。 总之逼格有点高)。   为什么说它们是最懒的呢?  因为它们没有自己去读取啊 哈哈哈。  事实上,它们最后都调用的是他们的父类InputStream去实现read()方法,但是总的有人去read()啊。  “额,我才不管呢,要么你就乖乖的读取给我,我给你封装一下,要么你就别用我们。”它们如是说。

    在这个过程中,我算好好理解了一下buffer这个东西了(说法可能不严谨,因为buffer事实上是一个抽象基类,而且还不是属于io包下的)。但是它们都有一些基本的特征:   那就是既然是缓冲,其自然是为了减少峰值的。   所以它们必须都有一个东西来装,类似于半山腰的水池。  那么这个水池实际上是个什么玩意呢?    万法归宗,万本归源, 自然是byte[]。  嘿嘿嘿,而且是位于内存中,也就是java内部。    我还发现一个不成文的规定,那就是它们有一个默认大小,就是 2的十三次方,也就是8192.。   还有我们平常见过特别多的,flush()方法。   但是BufferInputStream里面好像不叫flush(),叫做fill()。  那就等到了包装流的时候再继续介绍吧。 (其本质也是调用read()核心方法嘿嘿。)

          不知不觉都到了寝室要关门的时候了,正写的起劲呢。哎,连InputStream都没有写完太失望了。  回寝室继续写吧,争取忙完。

    原文作者:java源码分析
    原文地址: https://blog.csdn.net/qq_36285943/article/details/79821811
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞