资讯专栏INFORMATION COLUMN

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

Flink_China / 830人阅读

摘要:通过多个装饰类实现责任链模式,它将对一个输入流的不同处理分散到不同的中去。

1、基本概念 1.1、InputStream

最基本的字节输入流,抽象类,定义了读取原始字节的所有基本方法
1.1.1、public abstract int read() throws IOException 读取一个字节的方法,最基础的方法
1.1.2、public int read(byte b[], int off, int len) 读取指定长度的字节到字节数组,基于方法1.1.1
1.1.3、public int read(byte b[]) throws IOException 读取一个数组那么多的字节,基于 方法1.1.2
1.1.4、public long skip(long n) throws IOException 跳过一定字节数,用到的比较少
1.1.5、public int available() throws IOException 返回可以读取的最少字节数,用到的比较少
1.1.6、mark(int readlimit)、void reset()和markSupported()这三个方法,并不是每个子类都支持,这里设计得不合理,完全可以把这三个方法迁移到一个新的接口中去。
1.1.7、public void close() throws IOException 关闭输入流

1.2、OutputStream

最基本的字节输出流,抽象类,定义了写入原始字节的所有基本方法
1.2.1、public abstract void write(int b) throws IOException 写入一个字节,最基础的方法
1.2.2、public void write(byte b[], int off, int len) throws IOException 将一个字节数组中的部分字节写入,基于方法1.2.1
1.2.3、public void write(byte b[]) throws IOException 将一个字节数组写入,基于方法1.2.2
1.2.4、public void close() throws IOException 关闭输出流
1.2.5、public void flush() throws IOException 刷新输出流

点评:InputStream和OutputStream定义了I/O领域最基础的行为,也就是读取和写入一个字节,同时使用了模板方法将读取和写入的行为进行了适当扩展。

2、扩展点一:对I/O流的继承

有了抽象类,就一定会有子类。针对不同的数据来源,InputStream和OutputStream存在三种子类:一种是基于内存的ByteArrayInputStream/ByteArrayOutputStream,一种是基于磁盘文件的FileInputStream/FileOutputStream,还有一种是基于网络的SocketInputStream/SocketOutputStream。

2.1、FileInputStream/FileOutputStream

读取写入的源是操作系统的文件
FileInputStream使用native方法进行底层文件的读取private native int read0() throws IOException;所有其他的read方法最终都是基于这个本地方法实现。
FileOutputStream使用native方法进行底层文件的写入private native void writeBytes(byte b[], int off, int len, boolean append)所有其他的write方法都是基于这个本地方法实现。

2.2、ByteArrayInputStream/ByteArrayOutputStream

读取写入的源是内存的一个数组,用的比较少。

2.3、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的子类串联起来实现了责任链模式。

3.1、FilterInputStream/FilterOutputStream

FilterInputStream本身不实现输入流的功能,而是通过构造函数传入另一个InputStream的子类,把输入流的功能交给它做。通过继承FilterInputStream可以对输入输出流的行为进行扩展,这是装饰模式的典型用法。通过多个装饰类实现责任链模式,它将对一个输入流的不同处理分散到不同的FilterInputStream中去。FilterOutputStream和FilterInputStream的原理一样。

3.2、BufferedInputStream/BufferedOutputStream

继承了FilterInputStream,实现了输入流处理中的缓冲的功能。底层的流会先被读取到一个字节数组中,用户使用BufferedInputStream读取数据的时候,会先读取字节数组中的数据,读完了才会调用底层的流进行进一步的读取。这种方法可以提升读取的性能。
继承了FilterOutputStream,实现了输出流处理中的缓冲功能。当用户写入数据的时候,其实是先写入到BufferedOutputStream的一个字节数组中,当这个字节数组满了,才会真正调用底层的输出流执行输出动作。这种方法可以提升写入的性能。在使用BufferedOutputStream的写入功能时,一定要使用flush,因为缓冲数组不满的时候是不会写入底层流的,在写入最后一点数据的时候,缓冲数据不一定被填满,这时候就需要调用flush进行强制刷新。

3.3、PrintStream

继承FilterOutputStream,这个类的print和println方法可以把java的一些基本类型数据转换成字节写入到底层输出流,但是PrintStream对String的转换是平台相关的,不同的平台会有不同的编码,所以写入到底层的字节也不同,因此PrintStream只适合于测试输出,不适合于一般的I/O操作,特别是网络流。

3.4、DataInputStream/DataOutputStream

这两个类继承了FilterInputStream/FilterOutputStream,用来实现将java基本类型转换成二进制来进行读写操作,这两个类的readUTF和writeUTF方法使用了一种特殊的UTF编解码方式,只能用于java程序,因此不建议在网络流或者跨平台的应用中使用者两个类

3.5、PushbackInputStream

继承了FilterInputStream,提供了一种回退的机制,可以实现unread,本质是使用缓冲数组实现了,也就是说,回退的范围是有限的。

4、Reader/Writer出现的原因

InputStream和OutputStream是面向字节的,而人类的习惯是面向字符,因此InputStream和OutputStream对于程序猿的用户体验不是太好,于是就需要提供一些面向字符的流。由于DataInputStream/DataOutputStream在跨平台的情况下存在问题,因此,java设计者干脆仿照InputStream和OutputStream重新设计了一套面向字符的I/O,也就是Reader/Writer

4.1、Reader

基本的字符输入流,是个抽象类
4.1.1、public abstract int read() throws IOException 读取一个字符的方法,最基础的方法
4.1.2、public int read(char b[], int off, int len) 读取指定长度的字符到字节数组,基于方法4.1.1
4.1.3、public int read(char b[]) throws IOException 读取一个数组那么多的字符,基于 方法4.1.2
4.1.4、public long skip(long n) throws IOException 跳过一定字符,用到的比较少
4.1.5、public int available() throws IOException 返回可以读取的最少字符,用到的比较少
mark(int readlimit)、void reset()和markSupported()这三个方法,并不是每个子类都支持,这里设计得不合理,完全可以把这三个方法迁移到一个新的接口中去。
4.1.6、public void close() throws IOException 关闭输入流
4.1.7、public boolean ready() throws IOException 是否已经准备好

4.2、Writer

基本的字符输出流,是个抽象类
4.2.1、abstract public void write(char cbuf[], int off, int len) 抽象方法,用于写入一个字符数组的一部分,需要子类实现
4.2.2、public void write(char cbuf[]) throws IOException 基于4.2.1、,写入一个字符数据
4.2.3、public void write(int c) throws IOException 将一个int类型的堤16位作为一个字符写入,基于4.2.1
4.2.4、public void write(String str) throws IOException 写入一个字符串,基于4.2.1
4.2.5、public void write(String str, int off, int len) throws IOException 写入一个字符串的一部分,基于4.2.1

5、字符与字节之间的转换 5.1、InputStreamReader/OutputStreamWriter

由于计算机只识别字节,所以Reader/Writer的数据来源最终还是字节,而他们无法直接和字节打交道,这时候就需要一个中介者将Reader/Writer和InputStream和OutputStream进行打通,于是就有了InputStreamReader和OutputStreamWriter

6、对Reader/Writer的继承

不同源的Reader/Writer,他们都继承InputStreamReader/OutputStreamWriter

6.1、FileReader/FileWriter

继承了InputStreamReader/OutputStreamWriter,传入FileInputStream/FileOutputStream作为底层的字节I/O

6.2、CharArrayReader/CharArrayWriter

继承了InputStreamReader/OutputStreamWriter,使用char数组作为数据源,用的比较少

7、对Reader/Writer行为的扩展

类似于字节流,也使用了装饰模式和责任链模式

7.1、FilterReader/FilterWriter

对Reader/Writer的代理,底层使用其他Reader/Writer作为真正的操作。

7.2、BufferedReader/BufferedWriter

继承了FilterReader/FilterWriter,BufferedReader使用char数组作为数据的缓冲区,读取数据先从缓存区读,读不到在从底层的Reader读,Reader其实用到是更底层的InputStream,尽量不要用BufferedInputStream作为底层InputStream,两层缓冲区没有必要。BufferedWriter先写入缓冲区,待缓冲区写满了再使用底层真正的Writer写,Writer其实用的是更底层的OutputStream。尽量不要用BufferedOutputStream作为底层OutputStream,两层缓冲区没必要。

7.3、PushbackReader

继承了FilterReader,实现了可退回的写,本质是使用了一个char数组,所以可退回是有界限。

7.4、PrintWriter

用于取代PrintStream,它可以java基本类型转换成字节输出,而且可以正确处理不同字符集的国际化问题。

至此,我们对java.io包下的相关类都做了详细的解读,接下来,让我们看看第三方开源框架都对java IO进行了哪些扩展。

8、开源库对Java IO的扩展

通过上面的解读我们知道,java IO本身的扩展点有两个,一个是通过继承对数据来源的扩展,第二个是通过装饰模式对行为进行扩展。下面介绍的commons-io选择了对行为进行扩展,并提供一些IO操作的工具方法,简化IO操作,而okio则不走寻常路,废弃了java IO的体系,设计出了source/sink接口体系。

8.1、commons-io

8.1.1、扩展行为
最新的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命令

8.1.2、工具方法
IOUtils工具类,主要提供以下工具方法:

closeQuietly - 关闭一个流,忽略异常

toXxx/read - 从某个流读取数据

write - 向某个流写入数据

copy -从一个流复制到另一个流

contentEquals - 比较两个流中的内容

8.2、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
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/66511.html

相关文章

  • 第十五章 输入输出系统

    摘要:在包下主要包括输入输出两种流,每种输入输出流又可分为字节流和字符流两大类。输入输出是从程序运行所在的内存的角度而言的。的输入流主要由和作为基类,而输出流主要由和作为基类。 本章主要参考和摘自疯狂java讲义上面的(java编程思想的后面看过后有新的内容再补充进去吧)。  输入输出是所有程序都必需的部分————使用输入机制允许程序读取外部数据(包括磁盘、光盘等存储设备上的数据和用户输入的...

    hankkin 评论0 收藏0
  • Java IO (一),理解

    摘要:的是实现输入输出的基础中把不同的输入输出源键盘文件网络连接抽象的表述为流流的分类输入流和输出流按照流的流向来分输入流只能从中读数据而不能向其中写数据输出流只能向其中写出数据而不能从中读取数据此处的输入输出涉及到一个方向问题数据从内存到硬盘被 Java的IO是实现输入输出的基础,Java中把不同的输入/输出源(键盘,文件,网络连接)抽象的表述为流,stream. 流的分类 输入流和输...

    罗志环 评论0 收藏0
  • 我的阿里之路+Java面经考点

    摘要:我的是忙碌的一年,从年初备战实习春招,年三十都在死磕源码,三月份经历了阿里五次面试,四月顺利收到实习。因为我心理很清楚,我的目标是阿里。所以在收到阿里之后的那晚,我重新规划了接下来的学习计划,将我的短期目标更新成拿下阿里转正。 我的2017是忙碌的一年,从年初备战实习春招,年三十都在死磕JDK源码,三月份经历了阿里五次面试,四月顺利收到实习offer。然后五月怀着忐忑的心情开始了蚂蚁金...

    姘搁『 评论0 收藏0
  • 乐字节Java之file、IO基础知识和操作步骤

    摘要:流分类由此可见,流很庞大从不同角度进行分类数据分类按处理数据单位分为字节流和字符流。处理数据是音频视频文本等一切为字节流,仅能处理文本的为字符流。功能分类节点流和处理流。从向一个特定的设备磁盘网络等读写数据的流称为节点流,也常被称为低级流。 嗨喽,小乐又来了,今天要给大家送上的技术文章是Java重点知识-IO流。 先来看看IO流的思维导图吧。showImg(https://segmen...

    pkhope 评论0 收藏0
  • Java问题汇总,持续更新到GitHub

    摘要:目录介绍问题汇总具体问题好消息博客笔记大汇总年月到至今,包括基础及深入知识点,技术博客,学习笔记等等,还包括平时开发中遇到的汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善开源的文件是格式的同时也开源了生活博客,从年 目录介绍 00.Java问题汇总 01.具体问题 好消息 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技...

    beita 评论0 收藏0

发表评论

0条评论

Flink_China

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<