Java IO总结

IO总结

  • 比特/字节/字符
    • 比特(bit) =(经过分组编码)=> 字节(Byte) =(经过字符集编码)=> 字符
    • 1.比特(Bit):最小的二进制单位 ,是计算机的操作部分。取值0或者1
    • 2.字节(Byte):是计算机操作数据的最小单位由8位bit组成 取值(-128-127)
    • 3.字符(Char):是用户的可读写的最小单位,在Java里面由16位bit组成 取值(0-65535)
  • IO和流
    • IO是指计算机与外部世界或者一个程序与计算机的其余部分的之间的接口。在 Java 编程中,直到最近一直使用 流 的方式完成 I/O。所有 I/O 都被视为单个的字节的移动,通过一个称为 Stream 的对象一次移动一个字节。流 I/O 用于与外部世界接触。它也在内部使用,用于将对象转换为字节,然后再转换回对象
    • 流是一串连续不断的数据的集合,就象水管里的水流,在水管的一端一点一点地供水,而在水管的另一端看到的是一股连续不断的水流。数据写入程序可以是一段、一段地向数据流管道中写入数据,这些数据段会按先后顺序形成一个长的数据流。对数据读取程序来说,看不到流在写入时的分段情况,每次可以读取其中的任意长度的数据,但只能先读取前面的数据后,再读取后面的数据。
  • 流的分类
    • 1.按照流中操作单元划分
      • 字节流:操作byte类型数据,数据单元是8位的字节。主要操作类是OutputStream、InputStream的子类;不用缓冲区,直接对文件本身操作。
      • 字符流:操作字符类型数据,字符流操作的是数据单元为16位的字符。主要操作类是Reader、Writer的子类;使用缓冲区缓冲字符,不关闭流就不会输出任何内容。
    • 2.按照数据的流向划分
      • 输入流:数据从硬盘到内存的数据流
      • 输出流:数据从内存到硬盘的数据流
    • 3.字节流与字符流的转换
      • OutputStreamWriter:是Writer的子类,将输出的字符流变为字节流,即将一个字符流的输出对象变为字节流输出对象。
      • InputStreamReader:是Reader的子类,将输入的字节流变为字符流,即将一个字节流的输入对象变为字符流的输入对象。
  • Java中IO的分类
    • BIO
      • Block IO,即同步并阻塞的IO,是传统java.IO包下的代码实现
      • 服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理
      • 如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
    • NIO
      • 以块的方式实现IO,将最耗时的 I/O 操作(即填充和提取缓冲区)转移回操作系统,因而可以极大地提高速度。
      • 但相应的缺少面向流的IO所具有的优雅性与简单性。
      • 同步非阻塞IO
        • 服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器(selector)上,所有的连接实际上只对应一个线程.
      • 异步阻塞IO
        • 应用发起一个IO操作以后,不等待内核IO操作的完成,等内核完成IO操作以后会通知应用程序
        • 通过select系统调用来完成的,而select函数本身的实现方式是阻塞的,而采用select函数有个好处就是它可以同时监听多个文件句柄
      • 重要概念
        • Channel
          • 通道,是双向的,可读,写或同时用于读写
          • 常用的通道类型
            • FileChannel,一般用于本地的读写数据。
              • 无法被Selector复用,无法注册到Selector,无法切换到非阻塞状态
            • SelectableChannel,一般用于注册到Selector上被监听事件
              • 可以切换为非阻塞configureBlocking(false)
        • Buffer
          • 底层依然是数组,但是缓冲区实现了对数据读写的灵活性,因此必须理解一下4个概念
            • mark <= position <= limit <= capacity
            • position:当前读写到的位置。比如读了5个字节,则position为5,指向数组的第6个位置。
            • limit:表明(从缓冲区写入通道)还有多少数据需要取出,(从通道写入缓冲)还有多少空间可以放入
            • capacity:缓冲区的最大容量,即数组的大小
            • mark:标志位,记录下当前位置,初始为-1
          • 最常见的是ByteBuffer。但java基本类型都有对应的Buffer,操作都是完全一样的,只是操作的数据类型不同
          • 通用方法
            • mark():标记当前位置,将position赋值给mark,即标记当前位置;
            • reset():mark不小于0,将mark重新赋值给position
            • clear():清空缓冲区,实际上初始化mark=-1 ,position=0和limit=capacity,让数据写入缓冲区从头开始,覆盖原有的
            • filp():切换为读,即从写入缓冲到读取缓冲,limit=position,mark=-1,position=0,
            • rewind():倒回初始,重新开始读取缓冲,即position=0,mark=-1,limit不变
            • remaining():返回limit-position
            • 至于分配空间与获取数据等操作,与实现类操作的数据类型和底层数组类型有关
          • 只读缓冲区:只读,不写。无法从只读转为可写的缓冲区。调用asReadOnlyBuffer()返回一个原缓冲区相同的只读缓冲区,共享数据。
          • 缓冲区分片:调用slice()以原缓冲区的position和limit为起始和结束位,生成一个与原缓冲区共享区间数据的分片缓冲区。
        • Selector
          • 通道注册到选择器上,选择器监控通道
          • 当某一个通道上,某一个事件准备就绪时,那么选择器才会将这个通道分配到服务器端一个或多个线程上,再继续运行。
          • 选择器的使用
            • 1.创建一个Selector: Selector.open()
            • 2.注册通道:channel.register(Selector sel, int ops),ops为监听事件的选择键(SelectionKey)
              • 读 : SelectionKey.OP_READ ( 1)
              • 写 : SelectionKey.OP_WRITE ( 4)
              • 连接 : SelectionKey.OP_CONNECT ( 8)
              • 接收 : SelectionKey.OP_ACCEPT ( 16)
              • 若注册多个事件,用“|”连接
      • 阻塞实现

      • 非阻塞实现

    • AIO
      • 异步非阻塞IO
      • 用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。
      • 示例,以AsynchronousFileChannel为例

    • 三者的适用场景
      • BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
      • NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
      • AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
  • 阻塞与同步
    • 同步与异步
      • 同步,异步,是描述被调用方的。
      • 调用方在得到返回之前所做的事情不一样。
    • 阻塞与非阻塞
      • 阻塞,非阻塞,是描述调用方的。
      • 被调用方对于发起请求的处理不一样.
      • 如果是阻塞,A在发出调用后,要一直等待,等着B返回结果。
      • 如果是非阻塞,A在发出调用后,不需要等待,可以去做自己的事情。
  • IO模型
    • 阻塞式IO模型
      • 进程发起IO系统调用后,进程被阻塞,转到内核空间处理,整个IO处理(等待数据准备,然后再拷贝到用户空间)完毕后返回进程。操作成功则进程获取到数据。
      • 特点
        • 进程阻塞挂起不消耗CPU资源,及时响应每个操作;
        • 实现难度低、开发应用较容易;
        • 适用并发量小的网络应用开发;
    • 非阻塞IO模型
      • 阻塞与非阻塞描述的是用户线程调用内核IO操作的方式。
      • 用户进程发起IO系统调用后,如果为空数据,不会进行等待,而是会立即返回未准备就绪的信号.用户程序根据情况执行下一步操作.
      • 但用户程序实际上还是阻塞在请求内核的这个阶段上.
    • IO复用模型
      • 多路复用IO模型是目前使用得比较多的模型。Java NIO实际上就是多路复用IO。
      • 在多路复用IO模型中,会有一个线程不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的IO读写操作。
      • 系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有socket读写事件进行时,才会使用IO资源,减少了资源占用。
      • 相比较于 多线程+阻塞IO,减少一个连接对应一个线程的资源浪费.而且在一个线程中处理一个长连接的话,如果后面有很多连接,会造成性能瓶颈。而多路复用IO模式,通过一个线程就可以管理多个socket,只有当socket真正有读写事件发生才会占用资源来进行实际的读写操作。因此,多路复用IO比较适合连接数比较多的情况。
      • 多路复用IO为何比非阻塞IO模型的效率高?是因为在非阻塞IO中,不断地询问socket状态时通过用户线程去进行的。而在多路复用IO中,轮询每个socket状态是内核在进行的,这个效率要比用户线程要高的多。
      • 多路复用IO模型是通过轮询的方式来检测是否有事件到达,并且对到达的事件逐一进行响应。因此如果没有事件,则一直阻塞在那里,因此这种方式会导致用户线程的阻塞。或则,一旦事件(IO读取,并不是指连接)响应体很大,那么就会导致后续的事件迟迟得不到处理,并且会影响新的事件轮询。
    • 信号驱动IO模型
      • 在信号驱动IO模型中,当用户线程发起一个IO请求操作,会给对应的socket注册一个信号函数,然后用户线程会继续执行。
      • 当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号之后,便在信号函数中调用IO读写操作来进行实际的IO请求操作。
      • 但实际上,线程还是阻塞在内核拷贝数据到用户线程的阶段.
    • 异步IO模型
      • 异步IO模型是比较理想的IO模型,在异步IO模型中,当用户线程发起read操作之后,立刻就可以开始去做其它的事。
      • 从内核的角度,当它受到一个asynchronous read之后,它会立刻返回,说明read请求已经成功发起了,因此不会对用户线程产生任何block。内核会等待数据准备完成,然后将数据拷贝到用户线程。完成后,内核会给用户线程发送一个信号,告诉它read操作完成了。
      • 在异步IO模型中,IO操作的两个阶段都不会阻塞用户线程,这两个阶段都是由内核自动完成,然后发送一个信号告知用户线程操作已完成。用户线程中不需要再次调用IO函数进行具体的读写。
      • 与信号驱动模型有所不同的,在信号驱动模型中,当用户线程接收到信号表示数据已经就绪,然后需要用户线程调用IO函数进行实际的读写操作;而在异步IO模型中,收到信号表示IO操作已经完成,不需要再在用户线程中调用iO函数进行实际的读写操作。
      • 注意,异步IO是需要操作系统的底层支持,在Java 7中,提供了Asynchronous IO。
    • 前面四种IO模型实际上都属于同步IO,只有最后一种是真正的异步IO,因为无论是多路复用IO还是信号驱动模型,IO操作的第2个阶段(将数据拷贝到用户线程)都会引起用户线程阻塞,也就是内核进行数据拷贝的过程都会让用户线程阻塞。

  • 参考资料
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值