简介
我们常和io打交道,这个类族里面牵涉到的类很多,经常让人觉得比较迷茫。在使用的时候也不容易找到合适的类来完成自己的任务。这里结合前面一篇讨论java io和设计模式相关的文章针对java io里几个主要io类型的类做了一个总结。通过对类的结构和关系的理顺,我们可以更好的使用现有io相关的类,在以后有必要的时候也可以去定制自己所需要的功能。
总体说明
在详细讨论每个具体类别之前,我们可以先回想一下自己以前使用java io的时候常碰到的一些用法。比如说我们在要读取一个文件的时候,我们常使用如下的形式:
InputStream in = new BufferedInputStream( new FileInputStream("test.txt"));
我们通过FileInputStream可以直接打开文件,如果我们想增加一些新的特性,我们用BufferedInputStream来包装了它一把。比较有意思的是它居然也是一个InputStream类型,还可以把它当成一个基类来使。这种用法的核心根源在于它采用的是decorator的设计模式。在我的这篇文章里对它的由来做了一个详细的分析。在一个decorator模式描述的场景里我们有一系列的核心类,相当于提供了若干基本的功能。同时,我们也有一系列的包装类,它是对现有类功能的增强,但是它不是一个独立的类,需要将一个核心类作为参数给它包装。我们以java io里的InputStream类族为例,它的主要类图结构如下图:
我们可以发现真正要扩展功能的点是都继承自类FilterInputStream。它只是一个简单的类代理,在它内部有一个指向InputStream类型的成员变量:
protected volatile InputStream in;
它的构造函数也只是相当于将一个InputStream类型的对象引入进来:
protected FilterInputStream(InputStream in) { this.in = in; }
从这里只看到这个类其实什么都没干,它只是做一个把功能代理给另外一个类做的示范。具体添加扩展功能的都是它的子类,我们就以它的子类BufferedInputStream为例来看看它是怎么增强功能的。
前面那部分的代码将定义的成员变量以及方法声明为protected,这说明这些继承它的子类是可以访问到这个InputStream成员变量的。我们以read方法为例看看它具体的增强:
public synchronized int read() throws IOException { if (pos >= count) { fill(); if (pos >= count) return -1; } return getBufIfOpen()[pos++] & 0xff; }
这个方法只是读取下一个字节,但是它内部采用了一个字节串作为缓冲。在读取的时候fill()方法实际上在填充整体的缓冲区。fill方法的实现虽然比较繁琐:
private void fill() throws IOException { byte[] buffer = getBufIfOpen(); if (markpos < 0) pos = 0; /* no mark: throw away the buffer */ else if (pos >= buffer.length) /* no room left in buffer */ if (markpos > 0) { /* can throw away early part of the buffer */ int sz = pos - markpos; System.arraycopy(buffer, markpos, buffer, 0, sz); pos = sz; markpos = 0; } else if (buffer.length >= marklimit) { markpos = -1; /* buffer got too big, invalidate mark */ pos = 0; /* drop buffer contents */ } else { /* grow buffer */ int nsz = pos * 2; if (nsz > marklimit) nsz = marklimit; byte nbuf[] = new byte[nsz]; System.arraycopy(buffer, 0, nbuf, 0, pos); if (!bufUpdater.compareAndSet(this, buffer, nbuf)) { // Can't replace buf if there was an async close. // Note: This would need to be changed if fill() // is ever made accessible to multiple threads. // But for now, the only way CAS can fail is via close. // assert buf == null; throw new IOException("Stream closed"); } buffer = nbuf; } count = pos; int n = getInIfOpen().read(buffer, pos, buffer.length - pos); if (n > 0) count = n + pos; }
但是它本质上是一个填充缓冲区数组的操作,同时通过int n = getInIfOpen().read(buffer, pos, buffer.length - pos);语句使用InputStream做了一个预读取操作。
我们前面举这个示例并不是要对它所有的代码做一个分析,而是针对decorator具体功能的实现做一个跟踪。在扩展类里,无非做的就是一个调用被包装对象的方法,同时将自己附加的功能也添加上去。相信有了这么一个说明,我们对它的具体实现理解更加深刻,在后面部分的说明也更加容易理解。
在java io里,InputStream, OutputStream, Reader, Writer的类结构都采用了decorator模式。
Stream
InputStream和OutputStream面向的是通用的字节流访问方式,对于文件读取和数据传输都可以使用。
下图是InputStream的类结构:
FilterInputStream就是里面包装类的基类,所有需要增强InputStream效果的类实现可以继承它。这里已经包含的有BufferedInputStream, CheckedInputStream等。我们如果要增加新功能的话也可以按照BufferedInputStream等类的方式继承FilterInputStream并实现里面的方法。这种类似的convension对于OutputStream也类似。
OutputStream的类结构图如下:
和前面一样,大同小异的结构。
Reader/Writer
Reader/Writer和前面不一样的地方在于他们是面向具体字符串的。在一定程度上他们要关注传输访问的内容。比如说前面的IOStream只要把一串字节给读过来就可以了,而这里要考虑到编码和具体内容的问题。在很多时候我们读写一些文本内容就需要用到这些。
下面分别是Reader和Writer的类结构:
File
和前面两种io方式不一样的地方是,前面的两种方式提供了一个比较高层别的抽象。他们访问的是面向流或者字符的输入输出接口。可以是文件,但是也可以是其他的,比如说USB口,网络socket接口等。而这里对于纯文件的访问,有一个专门的类就是File。
我们使用File的时候更多的是考虑对于一个个的具体文件来说,要看他们的具体属性,比如说产生和修改时间,文件大小,文件目录结构和层级等。所以说在一些比较直接对文件操作的场景下,它们是最常用的。
在用File类遍历文件目录的时候会突然有一种想法,感觉就是这里目录和文件是一种树形的结构,目录包含文件或者目录。我们也可以将目录当作一种文件来看,它只是一个其他文件的容器而已。File类是一个相对比较独立的类。由于File相对比较简单,就不详细讨论它的具体使用和实现了。
总结
我们通常使用的java io主要包含面向流的输入输出(InputStream, OutputStream)以及面向字符的输入输出(Reader, Writer)。采用的这两种输入输出的方式有一个重要的好处就是他们提供了足够的抽象,我们读写的对象可以不仅仅只是单纯的文件,可也以是网络socket, url,也可以是磁盘的块设备或者其他字符串等输入。因为这种抽象涵盖的意义是这么广,它的设计思想有点像unix里面的设计哲学,将各种复杂的输入输出都归结到一种统一的形式,方便使用和扩展。除了以上提到的这两种,如果我们需要对文件操作的话,最常用的是使用File或者RandomAccessFile对象。比如遍历文件或者访问文件某些内容。
我们前面访问io是采用阻塞式的,也就是说如果我们有一个线程去访问文件或者网络socket等,因为在内存中执行的线程速度和其他IO设备之间访问速度有很大的差别。这就导致io要执行完毕的时候而系统内的线程已经等了好一阵了。于是为了提高线程运行的效率引入了nio。这是一种非阻塞式的io。它可以保证当一个线程执行io的时候,在io执行完毕之前的这段时间它可以继续做自己的事情。关于nio的详细介绍我们会在后面有专门的文章讨论。
相关推荐
### Java I/O总结 #### 一、从`new BufferedReader(new InputStreamReader(conn.getInputStream()))`想到的 在Java编程中,处理输入输出(I/O)是一项常见的任务。这段代码`new BufferedReader(new ...
这是一个关于Java I/O的知识点总结,希望大家共同学习,共同进步
总结起来,Java I/O类库提供了广泛且灵活的工具,用于处理各种输入和输出需求。开发者可以根据具体需求选择合适的类和装饰器,构建出满足特定场景的输入输出系统。理解并熟练运用这些类和模式对于任何Java开发者来说...
### Java I/O 实例编程学习教程复习 #### 一、Java I/O 概述 Java I/O(输入/输出)是 Java 编程语言中处理数据流的核心技术之一。通过 I/O,我们可以读取文件、网络数据等外部资源,并将程序产生的数据写入到文件...
Java中的I/O流处理是程序与外部设备交互数据的关键机制,包括从文件、网络、内存等数据源读取数据和向这些目标写入数据。I/O流系统在Java的`java.io`包中被实现,提供了丰富的类和接口来支持各种类型的流操作。 **I...
Java I/O(输入/输出)是Java编程语言中不可或缺的一部分,它允许程序与外部资源进行数据交换,如文件、网络连接、系统...在学习过程中,不断实践和总结,理解不同流类的特性和适用场景,是提升Java I/O技能的关键。
这是一篇关于javaSe的基础班关于IO流的全套总结,希望能对你的基础学习有所帮助。
Java的I/O系统是Java平台的核心特性之一,用于处理数据的输入输出操作。这个系统包括一系列的类和接口,主要用于字节和字符的传输,涵盖了从简单文件操作到复杂的网络通信。在Java中,I/O操作是通过数据流的概念来...
有关Java输入输出流的总结有关Java输入输出流的总结有关Java输入输出流的总结
Java I/O系统是Java编程语言中的一个核心组成部分,它提供了处理输入输出操作的类和接口。这个系统的设计目的是为了使得应用程序能够与外部世界交互,包括读取和写入文件、网络数据、标准输入输出流等。在Java中,I/...
总结,MaglevIO是一个致力于提高Java I/O效率和易用性的库,它建立在NIO之上,提供了一种更高效、更简单的I/O操作方式。对于那些需要处理大量并发连接、追求高性能的Java应用来说,MaglevIO是一个值得考虑的工具。...
Java-I/O框架总结,思维导图
### Java文件I/O操作:读取与写入文件的全面指南 #### 一、引言 文件I/O(输入/输出)是编程中的一项基本技能,尤其是在处理数据持久化和系统交互时。Java提供了丰富的API来支持文件的读取和写入操作。本文将详细...
本文将对Java I/O进行详细的总结,包括基本概念、流的分类、缓冲区、转换流以及高级特性。 首先,理解Java I/O的基础是“流”。在Java中,流是一个数据序列,可以是字节流或字符流,用于读取或写入数据。流分为四大...
总结起来,通过结合Java的I/O流遍历文件和Android的ListActivity,我们可以实现查找并显示应用程序根目录下所有文件的功能。在实际开发中,还需要考虑权限管理(特别是对于Android 6.0及更高版本),以及对不同设备...
在这个总结中,我们将深入探讨Java I/O的几个关键知识点。 首先,我们要理解I/O的基本概念。I/O,即Input/Output,指的是计算机系统与外部设备之间交换数据的过程。在Java中,I/O操作主要涉及到java.io包中的类和...