欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

Android进阶之从IO到NIO的模型机制演进,

来源: 开发者 投稿于  被查看 43910 次 评论:200

Android进阶之从IO到NIO的模型机制演进,


目录
  • 引言
  • 1 Basic IO模型
    • 1.1 RandomAccessFile的缓冲区和BufferedInputStream缓冲区的区别
    • 1.2 Basic IO模型底层原理
  • 2 NIO模型
    • 3 OKIO

      引言

      其实IO操作相较于服务端,客户端做的并不多,基本的场景就是读写文件的时候会使用到InputStream或者OutputStream,然而客户端能做的就是发起一个读写的指令,真正的操作是内核层通过ioctl指令执行读写操作,因为每次的IO操作都涉及到了线程的操作,因此会有性能上的损耗,那么从本篇文章开始,我们将进入IO的世界,了解IO到NIO机制的演进,从底层关注序列化的原理。

      1 Basic IO模型

      那么在Java(Kotlin)中,IO主要分为两种:Basic IO 和 Net IO;Basic IO是我们在开发当中常用的一些IO流,例如:

      FileInputStream://文件输入流
      FileOutputStream://文件输出流
      BufferedInputStream://缓存字节输入流
      BufferedOutputStream://缓存字节输入流,此类数据流为了提高读写效率,可以缓存数据到buffer,通过flush一起写入;内核分配内存为一页4K,但是Java缓冲区默认是8K
      ObjectInputStream
      ObjectOutputStream:// 将数据序列化处理
      RandomAccessFile://提供位移数据插入
      

      对于前面的几个数据流,我就不介绍用法了,对于最后一个RandomAccessFile,我想简单介绍一下,因为很多伙伴们可能不知道RandomAccessFile的存在,这里曾经有个面试题:

      假设有一个5G的文件,我想在文章的末尾追加一段话,我该怎么处理?或者我指定任意位置添加一部分文字内容,该怎么处理?

      很多伙伴看到这个问题之后,一拍脑门说:先通过FileInputStream把文件读写进来,然后再在末尾追加一部分内容组合成新的字节流,然后再通过FileOutputStream写入到新的文件中。

      完蛋,直接pass掉!因为前提这里已经是5G的文件了,如果通过FileInputStream读写,大概率就会直接OOM! 所以如果知道RandomAccessFile的存在,这些就不是问题了。

      fun testAccessFile() {
          //file文件
          val file = File("/storage/emulated/0/NewTextFile.txt")
          val accessFile = RandomAccessFile(file, "rw")
          //先写一段
          val text = "IO主要分为两种:Basic IO 和 Net IO;"
          accessFile.write(text.toByteArray())
          //再等5s
          Thread.sleep(5000)
          accessFile.seek(5)
          accessFile.write("seek to pos 5".toByteArray())
          accessFile.close()
      }
      

      首先我们常见一个RandomAccessFile,传入要读写的文件,首先写入一段话,然后等到5s后,调用RandomAccessFile的seek方法,此时指针就是移动到了文件第五个字符的位置,然后又写入了一些文字。

      所以按照这种思想,回到前面的问题,即便是5G的文件,也不需要进行读写操作获取之前的全部数据就能够实现零内存追加;当然还有一个场景也会经常用到,就是断点续传。

      1.1 RandomAccessFile的缓冲区和BufferedInputStream缓冲区的区别

      首先我先简单介绍下BufferedInputStream的缓存区效果,系统内核缓存区默认为4K,当缓存区满4K之后会进行磁盘的写入;那么在Java中是对其做了优化处理,将缓存区变为8K,当缓存区超过8K之后,会将数据复制给到内核缓存。

      fun testBuffer() {
              val file = File("/storage/emulated/0/NewTextFile.txt")
              val bis = BufferedOutputStream(FileOutputStream(file))
              val text = "8888888888888888".toByteArray()
              bis.write(text, 0, text.size)
      //        bis.flush()
          }
      

      例如上面的案例,此时App的内存缓存区没有满,那么如果不调用flush,那么数据不会写到磁盘文件中,只有当缓冲区满了之后,才会复制到内核空间缓存区。

      fun testAccessFile() {
          //file文件
          val file = File("/storage/emulated/0/NewTextFile.txt")
          val accessFile = RandomAccessFile(file, "rw")
          //先写一段
          val text = "IO主要分为两种:Basic IO 和 Net IO;"
          accessFile.write(text.toByteArray())
          //再等5s
          Thread.sleep(5000)
          accessFile.seek(5)
          val channel = accessFile.channel
          val mapper = channel.map(FileChannel.MapMode.READ_WRITE, channel.position(), channel.size())
          mapper.put("seek to pos 5".toByteArray())
      }
      

      如果按照BufferedOutputStream的思想,我们往缓冲区写数据,没有flush就不会有复制的操作,那么我们实际看到的是数据还是写进去了。

      其实MappedByteBuffer,是提供了一个类似于mmap性质的能力,实现了App缓冲区与内核缓冲区的桥接或者映射。

      当App写入缓存数据的时候,直接映射到了内核缓存区,完成了磁盘的读写操作。

      1.2 Basic IO模型底层原理

      其实对于基础的IO模型,也就是Basic IO的实现是阻塞的,其实我们也可以自己验证,在主线程中进行读写操作就是阻塞的。

      那么对于IO来说,主要分为两个阶段:

      (1)数据准备阶段;这里是由Java实现的,写入到JVM中;

      (2)复制阶段;内核空间复制用户空间缓存数据,这部分需要调用内核函数(ioctl、sync),完成复制的工作。

      剩下的磁盘写入操作就完全是由内核完成的,如果对于读写操作有疑问的,可以去看看下面这篇对于Binder底层原理的介绍。

      Android Framework原理 -- Binder驱动源码分析

      对于传统的Socket来说,这种属于Net IO,本质也是阻塞性质的,例如App进程想要获取一些数据,

      上图展示了read操作的整个调度过程:

      (1)当App调用系统方法想要获取某些数据的时候,首先系统内核会等待数据从网络中到达,这个过程内核处于阻塞的状态

      (2)等到数据到达之后,就会将网络数据复制到用户空间的缓冲区中,并通知App进程复制数据成功,此时App中其他业务才能够继续执行。

      所以整个过程中,App处于阻塞状态,而在高并发的场景中(客户端很少,这里拿服务端来举例),例如10000QPS(每秒10000次查询操作),此时如果采用IO阻塞模型,带来的后果就是CPU极速拉满最终可能导致熔断,所以针对这种情况,出现了NIO模型。

      2 NIO模型

      相对于IO模型来说,NIO模型做的优化是通过轮询机制获取内核的数据等待状态,看下图:

      当一次询问发出之后,如果当前内核还是数据等待状态,那么内核空间会被”挂起“,此时App进程可以做其他的事情,等到下一次轮询时间到了之后,再次发起询问,如果此时已经拿到了数据,那么就会进行复制操作,将数据放入用户进程缓冲区。

      那么对此,java.nio包下提供了很多非阻塞IO的API,例如我们前面提到的MappedByteBuffer。其实还是前面我们探讨的一个问题,在Android的场景下,很难碰到高并发的场景,所以基本上也很难用到这个,但是对于NIO模型的原理我们需要掌握透彻,在面试中可能会涉及到这些问题。

      3 OKIO

      最后介绍一个IO模型---OKIO,如果使用到OkHttp的伙伴们应该已经见到过这个,但是没有实际地去研究,为啥要引入这个okio三方库。

      首先okio是OkHttp团队基于Basic IO研发的一套自己的IO体系,为啥要搞一个这个玩意出来呢?通过前面我们分析Basic IO存在的一些问题,首先 Basic IO是阻塞的,而且在客户端端如果频繁地进行网络请求,而且网络请求是双向的,从客户端发出请求,服务端返回响应,那么这个过程必定会使用到InputStream和OutputStream。

      因为OkHttp是有自己的缓存策略的,如果使用到缓存,那么对于InputStream就需要一个buffer,对于OutputStream也需要一个buffer,每次读写操作都需要两个buffer来做支撑,因此针对这种场景,okio在底层做了处理。

      具体的处理就是不再使用byte[]数组存储数据,而是采用Segment数据结构。有熟悉Segment的伙伴应该知道,它是一个数组的双向链表,其中data就是一个byte数组,其中有next和pre两个指针。

      internal class Segment {
        @JvmField val data: ByteArray
        /** The next byte of application data byte to read in this segment.  */
        @JvmField var pos: Int = 0
        /** The first byte of available data ready to be written to.  */
        @JvmField var limit: Int = 0
        /** True if other segments or byte strings use the same byte array.  */
        @JvmField var shared: Boolean = false
        /** True if this segment owns the byte array and can append to it, extending `limit`.  */
        @JvmField var owner: Boolean = false
        /** Next segment in a linked or circularly-linked list.  */
        @JvmField var next: Segment? = null
        /** Previous segment in a circularly-linked list.  */
        @JvmField var prev: Segment? = null
      

      当进行读写操作的时候,都会往Segment中写入,就是将InputStream和OutputStream需要创建的缓冲区合并。

      这里需要说明一点,okio属于OkHttp内部核心IO框架,并不是单独拿出来任意业务方可以使用,所以对于okio的具体实现原理,后续会放在OkHttp框架原理中做详细的介绍。

      以上就是Android进阶之从IO到NIO的模型机制演进的详细内容,更多关于Android模型从IO到NIO机制的资料请关注3672js教程其它相关文章!

      您可能感兴趣的文章:
      • Android隐私协议提示弹窗的实现流程详解
      • Android Jetpack组件ViewModel基本用法详解
      • Android进阶从字节码插桩技术了解美团热修复实例详解
      • Android进阶Handler应用线上卡顿监控详解
      • Android进阶KOOM线上APM监控全面剖析
      • Android进阶CoordinatorLayout协调者布局实现吸顶效果
      • Android进阶NestedScroll嵌套滑动机制实现吸顶效果详解

      用户评论