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

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

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

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

分享:
  4、Reader/Writer出现的原因
  InputStream和OutputStream是面向字节的,而人类的习惯是面向字符,因此InputStream和OutputStream对于程序猿的用户体验不是太好,于是就需要提供一些面向字符的流。由于DataInputStream/DataOutputStream在跨平台的情况下存在问题,因此,java设计者干脆仿照InputStream和OutputStream重新设计了一套面向字符的I/O,也就是Reader/Writer
  Reader
  基本的字符输入流,是个抽象类
  4.1.1、public abstract int read() 读取一个字符的方法,最基础的方法
  4.1.2、public int read(char b[], int off, int len) 读取指定长度的字符到字节数组,基于方法4.1.1
  4.1.3、public int read(char b[])读取一个数组那么多的字符,基于 方法4.1.2
  4.1.4、public long skip(long n) 跳过一定字符,用到的比较少
  4.1.5、public int available()  返回可以读取的最少字符,用到的比较少
  mark(int readlimit)、void reset()和markSupported()这三个方法,并不是每个子类都支持,这里设计得不合理,完全可以把这三个方法迁移到一个新的接口中去。
  4.1.6、public void close() 关闭输入流
  4.1.7、public boolean ready() 是否已经准备好
  Writer
  基本的字符输出流,是个抽象类
  4.2.1、abstract public void write(char cbuf[], int off, int len) 抽象方法,用于写入一个字符数组的一部分,需要子类实现
  4.2.2、public void write(char cbuf[])  基于4.2.1、,写入一个字符数据
  4.2.3、public void write(int c)  将一个int类型的堤16位作为一个字符写入,基于4.2.1
  4.2.4、public void write(String str) 写入一个字符串,基于4.2.1
  4.2.5、public void write(String str, int off, int len) 写入一个字符串的一部分,基于4.2.1
  字符与字节之间的转换
  由于计算机只识别字节,所以Reader/Writer的数据来源最终还是字节,而他们无法直接和字节打交道,这时候就需要一个中介者将Reader/Writer和InputStream和OutputStream进行打通,于是就有了InputStreamReader和OutputStreamWriter
  5、对Reader/Writer的继承
  不同源的Reader/Writer,他们都继承InputStreamReader/OutputStreamWriter
  FileReader/FileWriter
  继承了InputStreamReader/OutputStreamWriter,传入FileInputStream/FileOutputStream作为底层的字节I/O
  CharArrayReader/CharArrayWriter
  继承了InputStreamReader/OutputStreamWriter,使用char数组作为数据源,用的比较少
  6、对Reader/Writer行为的扩展
  类似于字节流,也使用了装饰模式和责任链模式
  FilterReader/FilterWriter
  对Reader/Writer的代理,底层使用其他Reader/Writer作为真正的操作。
  BufferedReader/BufferedWriter
  继承了FilterReader/FilterWriter,BufferedReader使用char数组作为数据的缓冲区,读取数据先从缓存区读,读不到在从底层的Reader读,Reader其实用到是更底层的InputStream,尽量不要用BufferedInputStream作为底层InputStream,两层缓冲区没有必要。BufferedWriter先写入缓冲区,待缓冲区写满了再使用底层真正的Writer写,Writer其实用的是更底层的OutputStream。尽量不要用BufferedOutputStream作为底层OutputStream,两层缓冲区没必要。
  PushbackReader
  继承了FilterReader,实现了可退回的写,本质是使用了一个char数组,所以可退回是有界限。
  PrintWriter
  用于取代PrintStream,它可以java基本类型转换成字节输出,而且可以正确处理不同字符集的国际化问题。
  至此,我们对java.io包下的相关类都做了详细的解读,接下来,让我们看看第三方开源框架都对java IO进行了哪些扩展。
  7、开源库对Java IO的扩展
  通过上面的解读我们知道,java IO本身的扩展点有两个,一个是通过继承对数据来源的扩展,第二个是通过装饰模式对行为进行扩展。下面介绍的commons-io选择了对行为进行扩展,并提供一些IO操作的工具方法,简化IO操作,而okio则不走寻常路,废弃了java IO的体系,设计出了source/sink接口体系。
  commons-io
  扩展行为
  最新的commons-io 2.5提供了对input和output的各种扩展,通过继承FilterInputStream/FilterOutputStream实现
  input:
  AutoCloseInputStream:当IO流读到EOF时,会进行自动关闭
  BOMInputStream:用于处理含有BOM的输入流,比如Windows下用记事本保存的文件
  BoundedInputStream:含有读取界限的输入流,超过这个界限读取将会停止
  CountingInputStream:含有统计功能的输入流
  DemuxInputStream:这个输入流会将真正的流保存在ThreadLocal中
  ProxyInputStream:一个抽象类,提供了读取一个字节之前后之后的回调方法
  TaggedInputStream:这个类在抛异常的时候会给异常设置标记,从而用于跟踪异常
  TeeInputStream:从一个源读取数据,同时会保存到一个指定的源,类似于unix的tee命令
  UnixLineEndingInputStream:这个流在读取换行符的时候会按照unix格式读取
  WindowsLineEndingInputStream:这个流在读取换行符的时候会按照Windows格式读取
  output
  ChunkedOutputStream:写入流的时候按照chunk分批写入
  CountingOutputStream:具有统计功能的输出流
  DemuxOutputStream:这个输出流会将真正的流保存在ThreadLocal中
  ProxyOutputStream:一个抽象类,提供了写入一个字节之前后之后的回调方法
  TaggedOutputStream:这个类在抛异常的时候会给异常设置标记,从而用于跟踪异常
  TeeOutputStream:写数据到一个源,同时会保存到一个指定的源,类似于unix的tee命令
  工具方法IOUtils工具类,主要提供以下工具方法:
  closeQuietly - 关闭一个流,忽略异常
  toXxx/read - 从某个流读取数据
  write - 向某个流写入数据
  copy -从一个流复制到另一个流
  contentEquals - 比较两个流中的内容
  okio
  如果使用原生的Java IO进行基本类型的读写,我们需要使用DataInputStream/DataOutputStream以及BufferedReader/BufferedWriter这四个类,除此之外,我们还需要了解InputStreamReader/OutputStreamWriter以及Java IO之间的责任链,对于一般的Java开发者来说,这显然太复杂了。于是okio重新设计了接口Source/Sink,提供了访问基本类型的接口和缓冲功能,同时屏蔽了底层复杂的IO体系,开发者只要传入InputStream和OutputStream就可以了。
  具体的类关系如下:
  使用Okio的Java代码如下:
  try {
  BufferedSource bufferedSource = Okio.buffer(Okio.source(new FileInputStream("1.txt")));
  int i = bufferedSource.readInt();
  long l = bufferedSource.readLong();
  String s = bufferedSource.readString(Charset.forName("UTF-8"));
  BufferedSink bufferedSink = Okio.buffer(Okio.sink(new FileOutputStream("2.txt")));
  bufferedSink.writeInt(1);
  bufferedSink.writeLong(2L);
  bufferedSink.writeString("123", Charset.forName("UTF-8"));
  } catch (Exception e) {
  // process exception
  }
  8、总结
  最后我大致来还原一下Java设计者的心路历程:起初通过抽象类InputStream/OutputStream将I/O中的两大基本概念输入流和输出流的行为read/write固定下来,并通过模板方法丰富了read和write这两种最常用的方法。然后自然而然地使用继承去实现这两个抽象类。在计算机世界中最容易想到的输入流和输出流,一个是本地文件,一个是远程网络,当然Java设计者还设计了一个基于内存的流。接下来就是对输入输出流的行为read/write进行扩展,设计者通过装饰模式和责任链模式,在不改变接口的语义的情况下做到了对行为的扩展,于是就有了Java I/O特有的一层套一层的代码写法。当继承和设计模式对I/O进行扩展后,设计者发现,他设计的接口都是面向计算机的,也就是面向字节流的,对于开发者来说这套接口太难用了,开发者需要的是面向字符流的接口,于是他开始修修补补,设计了个DataInputStream/DataOutputStream,但是由于平台兼容性的原因失败了,于是干脆另起炉灶,重新设计了一套类似的接口体系Reader/Writer以及字节流与字符流之间的转换器InputStreamReader/OutputStreamWriter,从而解决了开发者的习惯问题。
22/2<12
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

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

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

沪ICP备05003035号

沪公网安备 31010102002173号