一、java I/O介绍
在操作系统操作中,有可能要读写一个文件,访问控制台,并在网络创建连接。在操作这些文件、控制台、网络的时候,采用的方式可能是顺序的、随机的、缓存的、二进制的、字符方式的、行的或者是字的。这里我们称文件、控制台、网络连接等就是I/O的源和目的;称缓存的、二进制的等为操作方式。这么多的源和目的,以及在这些源和目的上进行这么多复杂的操作,那么什么样的方案才能让这些源和目的之间能进行和管理交互呢?
java I/O 主要设计目标就是让客户端的Java程序不必知道最终的流源,流汇是磁盘上的文件还是数组等;也不必关心数据是否经过缓冲的,可否按照行号读取等处理的细节。于是他们对各种常见的流源,流汇以及处理过程的进行抽象化。他们很聪明的用“创建很多类”的办法来解决这个问题。Java在1.0版之后又对其I/O类库作了重大的修改,原先是面向byte的,现在又补充了面向Unicode字符的类库。为了提高性能,完善功能,JDK 1.4又加了一个nio(意思是“new I/O”)。因此,若想对Java的I/O类库有个全面了解,并且做到运用自如,就得先学习大量的类。
1、File 类 File类有一个极具欺骗性的名字;或许你会认为这是一个关于文件的类,但它不是,它是一个处理文件和目录的类。
你可以用它来表示某个文件的名字,也可以用它来表示目录里一组文件的名字。如果它表示的是一组文件,那么你还可以用list( )方法来进行查询,让它会返回String数组。
创建文件对象:
File path=new File(“d:\\java\\fitiny”);
File oldfile=new File(“d:\\java\\fitiny\\My.class”);
File的相关操作
private static void fileData(File f) {
if(f.exists()) {
System.out.println(f + " exists");
}else{
reurn;
}
System.out.println(
"Absolute path: " + f.getAbsolutePath() +
"\n Can read: " + f.canRead() +
"\n Can write: " + f.canWrite() +
"\n getName: " + f.getName() +
"\n getParent: " + f.getParent() +
"\n getPath: " + f.getPath() +
"\n length: " + f.length() +
"\n lastModified: " + f.lastModified());
if(f.isFile())
System.out.println("It's a file");
else if(f.isDirectory())
System.out.println("It's a directory");
}
2、输入与输出 I/O类库常使用"流(stream)"这种抽象。所谓"流"是一种能生成或接受数据的,代表数据的源和目标的对象。流把I/O设备内部的具体操作给隐藏起来了。
正如JDK文档所显示的,Java的I/O类库分成输入和输出两大部分。所有InputStream和Reader的派生类都有一个基本的,继承下来的,能读取单个或byte数组的read( )方法。同理,所有OutputStream和Writer的派生类都有一个基本的,能写入单个或byte数组的write( )方法。但通常情况下,你是不会去用这些方法的;它们是给其它类用的 —— 而后者会提供一些更实用的接口。因此,你很少会碰到只用一个类就能创建一个流的情形,实际上你得把多个对象叠起来,并以此来获取所需的功能。Java的流类库之所以会那么让人犯晕,最主要的原因就是"你必须为创建一个流而动用多个对象"。
我们最好还是根据其功能为这些class归个类。Java 1.0的类库设计者们是从决定"让所有与输入相关的类去继承InputStream"入手的。同理,所有与输出相关的类就该继承OutputStream了。
2.1 InputStream的种类
InputStream的任务就是代表那些能从各种输入源获取数据的类。这些源包括:
byte数组
String对象
文件
类似流水线的"管道(pipe)"。把东西从一头放进去,让它从另一头出来。
一个"流的序列(A sequence of other streams)",可以将它们组装成一个单独的流。 其它源,比如Internet的连接。
这些数据源各自都有与之相对应的InputStream的子类。此外,FilterInputStream也是InputStream的子类,其作用是为基类提供"decorator(修饰)"类,而decorator又是为InputStream配置属性和接口的。
2.2 OutputStream的种类
这部分都是些决定往哪里输出的类:是byte的数组(不能是String;不过你可以根据byte数组创建字符串)还是文件,或者是"管道"。
此外,FilterOutputStream还是decorator类的基类。它会为OutputStream安装属性和适用的接口。
2.3 InputStream 和 OutputStream的转换
public static byte[] copyBytesFromStream(InputStream is) throws Exception
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int read = 0;
if (null != is && is.available() >= 0) {
while ((read = is.read(buffer)) != 1) {
baos.write(buffer, 0, read);
}
}
else {
throw new IOException("IOException");
}
is.close();
return baos.toByteArray();
}
3、 Reader 和 Writer类系 Java 1.1对最底层的I/O流类库作了重大修改。第一次看到Reader和Writer的时候,你会觉得"它们大概是用来取代InputStream和OutputStream的"。但事实并非如此。虽然InputStream和OutputStream的某些功能已经淘汰了(如果你继续使用,编译器就会发警告),但它们仍然提供了很多很有价值的,面向byte的I/O功能,而Reader和Writer则提供了Unicode兼容的,面向字符的I/O功能。此外:Java 1.1还对InputStream和OutputStream作了新的补充,所以很明显这两个类系并没有被完全替代。
有时,你还必须同时使用"基于byte的类"和"基于字符的类"。为此,它还提供了两个"适配器(adapter)"类。InputStreamReader负责将InputStream转化成Reader,而OutputStreamWriter则将OutputStream转化成Writer。
Reader和Writer要解决的,最主要的问题就是国际化。原先的I/O类库只支持8位的字节流,因此不可能很好地处理16位的Unicode字符流。Unicode是国际化的字符集(更何况Java内置的char就是16位的Unicode字符),这样加了Reader和Writer之后,所有的I/O就都支持Unicode了。此外新类库的性能也比旧的好。
4、 数据源和目的 几乎所有的Java I/O流都有与之对应的,专门用来处理Unicode的Reader和Writer。但有时,面向byte的InputStream和OutputStream才是正确的选择;特别是java.util.zip;它的类都是面向byte的。所以最明智的做法是,先用Reader和Writer,等到必须要用面向byte的类库时,你自然会知道的,因为程序编译不过去了。
下面这张表格列出了这两个类系的数据源和目的之间的关系(也就是说,在这两个类系里,数据是从哪里来的,又是到那里去的)。
5、RandomAccessFile 用来访问据那些保存数记录的文件的,这样你就可以用seek( )方法来访问记录,并进行读写了。这些记录的大小不必相同;但是其大小和位置必须是可知的。
首先,你可能会不太相信,RandomAccessFile竟然会是不属于InputStream和OutputStream类系的。实际上,除了实现DataInput和DataOutput接口之外(DataInputStream和DataOutputStream也实现了这两个接口),它和这两个类系毫不相干,甚至都没有用InputStream和OutputStream已经准备好的功能;它是一个完全独立的类,所有方法(绝大多数都只属于它自己)都是从零开始写的。这可能是因为RandomAccessFile能在文件里面前后移动,所以它的行为与其它的I/O类有些根本性的不同。总而言之,它是一个直接继承Object的,独立的类。
基本上,RandomAccessFile的工作方式是,把DataInputStream和DataOutputStream粘起来,再加上它自己的一些方法,比如定位用的getFilePointer( ),在文件里移动用的seek( ),以及判断文件大小的length( )。此外,它的构造函数还要一个表示以只读方式("r"),还是以读写方式("rw")打开文件的参数 (和C的fopen( )一模一样)。它不支持只写文件,从这一点上看,假如RandomAccessFile继承了DataInputStream,它也许会干得更好。
只有RandomAccessFile才有seek方法,而这个方法也只适用于文件。BufferedInputStream有一个mark( )方法,你可以用它来设定标记(把结果保存在一个内部变量里),然后再调用reset( )返回这个位置,但是它的功能太弱了,而且也不怎么实用。
RandomAccessFile的绝大多数功能,如果不是全部的话,已经被JDK 1.4的nio的"内存映射文件(memory-mapped files)"给取代了。
二、I/O结构图 介绍了这么多类,会对I/O有一个初步的了解了吧。在介绍I/O结构之前,先来介绍一下java I/O中的重要模式Decorator和Adapter。
decorator就是修饰模式,它的主要目的就是在不改变任何原有类的功能的前提下,添加新的一些功能。它的结构一般如图(1):
在图(1)中,decorator和stream都继承了StreamInterface这个接口,decorator是一个装饰类,他封装了Stream这个类,拥有StreamInterface的所有功能,这样在不缺失原有功能的情况下,可以添加更多的功能。
java中很多类都是这样实现的,比如我们有个文件,需要创建其相应的流可以这样创建:
FielInputStream fis = new FileInputStream("d://fitiny/DecoratorA.class");然而我们现在有个需求就是要把这个file放进缓存中,我们可以这样实现 BufferedInputStream buffer = new BufferedInputStream(fis);其中BufferedInputStream就是一个修饰类。他和FielInputStream 都实现了InputStream这个接口。其他的decorator还有DataInputStream,PipedInputStream,FilterInputStream,SequnceInputStream。(这里只列出InputStream相关的)。好了,了解了decorator。我们再来看看Adapter。
Adapter模式主要用来将两个没有关系的类组合在一起使用,我们来先看一下图(2)
图(2)中,Adpatee和Stream是两个好无相干的类,然而有这样的一个功能就是需要实现Adpatee和Stream的功能。怎么办呢?java“类再生”中告诉我们,需要实现两个类组合可以用(composition)和(inheritance)。
那么我们可以这样实现:
//构建一个适配器类Adapter,继承Stream,并封装Adaptee类
public class Adapter extends Stream
{
//被适配器者
private Adaptee;
public Adapter(Adaptee adaptee)
{
this.adptee = adaptee;
}
//......
}
看到这边,你可想到java I/O中哪些类实现了Adapter模式?
在java I/O中的FileInputStream就是实现了Adapter,它继承了InputStream的功能,并对file进行了封装。从他的构造函数可以看出来FielInputStream fis = new FileInputStream("d://fitiny/DecoratorA.class");其他实现Adapter模式的类还有StringReader,DataInputStream等。
好了,现在我们结合以上两种模式来看看java I/O的结构图: