java io即java 输入输出系统。不管我们编写何种应用,都难免和各种输入输出相关的媒介打交道,其实和媒介进行io的过程是十分复杂的,这要考虑的因素特别多,比如我们要考虑和哪种媒介进行io(文件、控制台、网络),我们还要考虑具体和它们的通信方式(顺序、随机、二进制、按字符、按字、按行等等)。java类库的设计者通过设计大量的类来攻克这些难题,这些类就位于java.io包中。
在jdk1.4之后,为了提高java io的效率,java又提供了一套新的io,java new io简称java nio。它在标准java代码中提供了高速的面向块的io操作。本篇文章重点介绍java io,关于java nio请参考我的另两篇文章:
<a href="http://blog.csdn.net/suifeng3051/article/details/48160753">java nio详解(一)</a>
<a href="http://blog.csdn.net/suifeng3051/article/details/48441629">java nio详解(二)</a>
在java io中,流是一个核心的概念。流从概念上来说是一个连续的数据流。你既可以从流中读取数据,也可以往流中写数据。流与数据源或者数据流向的媒介相关联。在java io中流既可以是字节流(以字节为单位进行读写),也可以是字符流(以字符为单位进行读写)。
java的io包主要关注的是从原始数据源的读取以及输出原始数据到目标媒介。以下是最典型的数据源和目标媒介:
文件
管道
网络连接
内存缓存
system.in, system.out, system.error(注:java标准输入、输出、错误输出)
虽然java io类库庞大,但总体来说其框架还是很清楚的。从是读媒介还是写媒介的维度看,java io可以分为:
输入流:inputstream和reader
输出流:outputstream和writer
而从其处理流的类型的维度上看,java io又可以分为:
字节流:inputstream和outputstream
字符流:reader和writer
下面这幅图就清晰的描述了javaio的分类:
-
字节流
字符流
输入流
inputstream
reader
输出流
outputstream
writer
我们的程序需要通过inputstream或reader从数据源读取数据,然后用outputstream或者writer将数据写入到目标媒介中。其中,inputstream和reader与数据源相关联,outputstream和writer与目标媒介相关联。 以下的图说明了这一点:

上面我们介绍了java io中的四各类:inputstream、outputstream、reader、writer,其实在我们的实际应用中,我们用到的一般是它们的子类,之所以设计这么多子类,目的就是让每一个类都负责不同的功能,以方便我们开发各种应用。各类用途汇总如下:
文件访问
网络访问
内存缓存访问
线程内部通信(管道)
缓冲
过滤
解析
读写文本 (readers / writers)
读写基本类型数据 (long, int etc.)
读写对象
下面我们就通过两张图来大体了解一下这些类的继承关系及其作用
图1:java io 类的集成关系
图2:java io中各个类所负责的媒介
通过上面的介绍我们已经知道,字节流对应的类应该是inputstream和outputstream,而在我们实际开发中,我们应该根据不同的媒介类型选用相应的子类来处理。下面我们就用字节流来操作文件媒介:
例1,用字节流写文件
例2,用字节流读文件
同样,字符流对应的类应该是reader和writer。下面我们就用字符流来操作文件媒介:
例3,用字符流读文件
例4,用字符流写文件
字节流可以转换成字符流,java.io包中提供的inputstreamreader类就可以实现,当然从其命名上就可以看出它的作用。其实这涉及到另一个概念,io流的组合,后面我们详细介绍。下面看一个简单的例子:
例5 ,字节流转换为字符流
从上面字节流转换成字符流的例子中我们知道了io流之间可以组合(或称嵌套),其实组合的目的很简单,就是把多种类的特性融合在一起以实现更多的功能。组合使用的方式很简单,通过把一个流放入另一个流的构造器中即可实现,两个流之间可以组合,三个或者更多流之间也可组合到一起。当然,并不是任意流之间都可以组合。关于组合就不过多介绍了,后面的例子中有很多都用到了组合,大家好好体会即可。
file是java io中最常用的读写媒介,那么我们在这里就对文件再做进一步介绍。
例6 ,file操作
}
通过上面的例子我们已经知道,我们可以用fileinputstream(文件字符流)或filereader(文件字节流)来读文件,这两个类可以让我们分别以字符和字节的方式来读取文件内容,但是它们都有一个不足之处,就是只能从文件头开始读,然后读到文件结束。
但是有时候我们只希望读取文件的一部分,或者是说随机的读取文件,那么我们就可以利用randomaccessfile。randomaccessfile提供了<code>seek()</code>方法,用来定位将要读写文件的指针位置,我们也可以通过调用<code>getfilepointer()</code>方法来获取当前指针的位置,具体看下面的例子:
例7,随机读取文件
例8,随机写入文件
管道主要用来实现同一个虚拟机中的两个线程进行交流。因此,一个管道既可以作为数据源媒介也可作为目标媒介。
需要注意的是java中的管道和unix/linux中的管道含义并不一样,在unix/linux中管道可以作为两个位于不同空间进程通信的媒介,而在java中,管道只能为同一个jvm进程中的不同线程进行通信。和管道相关的io类为:pipedinputstream和pipedoutputstream,下面我们来看一个例子:
例9,读写管道
关于java io面向网络媒介的操作即java 网络编程,其核心是socket,同磁盘操作一样,java网络编程对应着两套api,即java io和java nio,关于这部分我会准备专门的文章进行介绍。
bufferedinputstream顾名思义,就是在对流进行写入时提供一个buffer来提高io效率。在进行磁盘或网络io时,原始的inputstream对数据读取的过程都是一个字节一个字节操作的,而bufferedinputstream在其内部提供了一个buffer,在读数据时,会一次读取一大块数据到buffer中,这样比单字节的操作效率要高的多,特别是进程磁盘io和对大量数据进行读写的时候。
使用bufferedinputstream十分简单,只要把普通的输入流和bufferedinputstream组合到一起即可。我们把上面的例2改造成用bufferedinputstream进行读文件,请看下面例子:
例10 ,用缓冲流读文件
关于如何设置buffer的大小,我们应根据我们的硬件状况来确定。对于磁盘io来说,如果硬盘每次读取4kb大小的文件块,那么我们最好设置成这个大小的整数倍。因为磁盘对于顺序读的效率是特别高的,所以如果buffer再设置的大写可能会带来更好的效率,比如设置成4*4kb或8*4kb。
还需要注意一点的就是磁盘本身就会有缓存,在这种情况下,bufferedinputstream会一次读取磁盘缓存大小的数据,而不是分多次的去读。所以要想得到一个最优的buffer值,我们必须得知道磁盘每次读的块大小和其缓存大小,然后根据多次试验的结果来得到最佳的buffer大小。
bufferedoutputstream的情况和bufferedinputstream一致,在这里就不多做描述了。
bufferedreader、bufferedwriter 的作用基本和bufferedinputstream、bufferedoutputstream一致,具体用法和原理都差不多 ,只不过一个是面向字符流一个是面向字节流。同样,我们将改造字符流中的例4,给其加上buffer功能,看例子: