从设计者的角度理解Java IO流

发表于:2017-2-13 10:33

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:biakia    来源:51Testing软件测试网采编

  摘要
  Java I/O是Java技术体系中非常基础的部分,它是学习Java NIO的基础。而深入理解Java NIO则是学习现代高性能网络通信框架(比如Netty)的基础。本文试图从设计者的角度去理解Java I/O流,从Java I/O的最初概念输入流/输出流开始,通过使用面向对象的各种扩展技术(继承、设计模式等等)逐渐展开对整个Java I/O的介绍,最后通过介绍第三方开源框架对Java I/O的扩展来结束本文。通过阅读本文,希望大家可以对整个Java I/O体系有一个整体的认识,可以从概念上而不是细节上对Java I/O的技术体系进行推导,知道Java I/O是什么,又能进一步知道为什么是这样而不是其他什么样。本文不会涉及具体的代码细节,如果对Java I/O体系的代码细节感兴趣,可以阅读JDK源代码。
  1、基本概念
  任何软件框架或者技术体系,回归到最初,一定有几个最基本的概念在支撑它的发展。Java I/O最基本的概念就是输入流和输出流,也就是InputStream和OutputStream。
  InputStream
  最基本的字节输入流,抽象类,定义了读取原始字节的所有基本方法
  1.1.1、public abstract int read()   读取一个字节的方法,最基础的方法
  1.1.2、public int read(byte b[], int off, int len) 读取指定长度的字节到字节数组,基于方法1.1.1
  1.1.3、public int read(byte b[])    读取一个数组那么多的字节,基于 方法1.1.2
  1.1.4、public long skip(long n)   跳过一定字节数,用到的比较少
  1.1.5、public int available()   返回可以读取的最少字节数,用到的比较少
  1.1.6、mark(int readlimit)、void reset()和markSupported()这三个方法,并不是每个子类都支持,这里设计得不合理,完全可以把这三个方法迁移到一个新的接口中去。
  1.1.7、public void close() 关闭输入流
  OutputStream
  最基本的字节输出流,抽象类,定义了写入原始字节的所有基本方法
  1.2.1、public abstract void write(int b) 写入一个字节,最基础的方法
  1.2.2、public void write(byte b[], int off, int len)将一个字节数组中的部分字节写入,基于基本方法1.2.1
  1.2.3、public void write(byte b[])  将一个字节数组写入,基于方法1.2.2
  1.2.4、public void close() 关闭输出流
  1.2.5、public void flush() 刷新输出流,由于操作系统会存在写入缓冲机制,也就是说,当我们调用write的时候,一般操作系统会把我们要写入的字节缓冲到缓冲区,然后在某个时间点一起刷新到本地磁盘,如果我们想要强制刷新,则需要调用这个方法。
  小结
  InputStream和OutputStream定义了I/O领域最基础的行为,也就是读取和写入一个字节,同时使用了模板方法将读取和写入的行为进行了适当扩展。
  2、扩展点一:对I/O流的继承
  InputStream和OutputStream都是抽象类,它们仅仅定义了I/O领域最基础的方法,但不涉及具体实现。针对不同的数据来源,InputStream和OutputStream存在三种实现:一种是基于内存的ByteArrayInputStream/ByteArrayOutputStream,一种是基于磁盘文件的FileInputStream/FileOutputStream,还有一种是基于网络的SocketInputStream/SocketOutputStream。
  FileInputStream/FileOutputStream
  读取写入的源是操作系统的文件FileInputStream使用native方法进行底层文件的读取
  private native int read0()
  所有其他的read方法最终都是基于这个本地方法实现。
  FileOutputStream使用native方法进行底层文件的写入
  private native void writeBytes(byte b[], int off, int len, boolean append)
  所有其他的write方法都是基于这个本地方法实现。
  ByteArrayInputStream/ByteArrayOutputStream
  读取写入的源是内存的一个数组,用的比较少。
  SocketInputStream/SocketOutputStream
  SocketInputStream使用 private native int socketRead0(FileDescriptor fd,byte b[], int off, int len,int timeout)这个native方法读取远程服务器的数据流。所有read方法都是基于这个本地方法实现的。 SocketOutputStream 使用private native void socketWrite0(FileDescriptor fd, byte[] b, int off,int len)这个native方法来进行远程数据流的写入,所有的write方法都是基于这个方法实现的。
  小结
  InputStream和OutputStream是对流的抽象,不同的具体流通过继承去实现,对于Java本地平台,最基本的就是基于文件系统的流,当涉及到远程系统,就会出现网络流,基于内存的流一般不会用到。
  3、扩展点二:对IO流行为的扩展
  装饰模式可以对一个类的行为进行扩展,并且不改变它的接口,Java通过FilterInputStream/FilterOutputStream实现了装饰模式。
  责任链模式则是定义统一的接口,然后通过多个实现该接口的子类串行协作完成一项复杂的功能。Java通过将多个FilterInputStream/FilterOutputStream的子类串联起来实现了责任链模式。
  FilterInputStream/FilterOutputStream
  FilterInputStream本身不实现输入流的功能,而是通过构造函数传入另一个InputStream的子类,把输入流的功能交给它做。通过继承FilterInputStream可以对输入输出流的行为进行扩展,这是装饰模式的典型用法。通过多个装饰类实现责任链模式,它将对一个输入流的不同处理分散到不同的FilterInputStream中去。FilterOutputStream和FilterInputStream的原理一样。
  BufferedInputStream/BufferedOutputStream
  继承了FilterInputStream,实现了输入流处理中的缓冲的功能。底层的流会先被读取到一个字节数组中,用户使用BufferedInputStream读取数据的时候,会先读取字节数组中的数据,读完了才会调用底层的流进行进一步的读取。这种方法可以提升读取的性能。继承了FilterOutputStream,实现了输出流处理中的缓冲功能。当用户写入数据的时候,其实是先写入到BufferedOutputStream的一个字节数组中,当这个字节数组满了,才会真正调用底层的输出流执行输出动作。这种方法可以提升写入的性能。在使用BufferedOutputStream的写入功能时,一定要使用flush,因为缓冲数组不满的时候是不会写入底层流的,在写入最后一点数据的时候,缓冲数据不一定被填满,这时候就需要调用flush进行强制刷新。
  PrintStream
  继承FilterOutputStream,这个类的print和println方法可以把java的一些基本类型数据转换成字节写入到底层输出流,但是PrintStream对String的转换是平台相关的,不同的平台会有不同的编码,所以写入到底层的字节也不同,因此PrintStream只适合于测试输出,不适合于一般的I/O操作,特别是网络流。
  DataInputStream/DataOutputStream
  这两个类继承了FilterInputStream/FilterOutputStream,用来实现将java基本类型转换成二进制来进行读写操作,这两个类的readUTF和writeUTF方法使用了一种特殊的UTF编解码方式,只能用于java程序,因此不建议在网络流或者跨平台的应用中使用者两个类
  PushbackInputStream
  继承了FilterInputStream,提供了一种回退的机制,可以实现unread,本质是使用缓冲数组实现了,也就是说,回退的范围是有限的。
  小结
  Java I/O设计者通过装饰模式和责任链模式扩展了I/O的行为,实现这种扩展的基石是FilterOutputStream和FilterInputStream。也正是因为这两种设计模式,让我们写的Java I/O代码变成了下面这样的:
  InputStream in = new GZIPInputStream(new BufferedInputStream(new FileInputStream("1.txt")));
  最里面的FileInputStream是真正的数据来源,而BufferedInputStream和GZIPInputStream都继承了FilterInputStream对I/O的行为进行了改变。
21/212>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号