Java I/O系统
1 Java I/O系统简介
Java类库的设计者通过创建大量的类来实现其强大的I/O系统,自从Java 1.0版本以来,Java的I/O类库发生了明显改变,在原来面向字节的类中添加了面向字符和基于Unicode的类,在JDK 1.4中,添加了nio类,添加进来是为了改进性能及功能。为了能够充分理解Java I/O系统以便正确地运用,我们需要学习相当数量的类。
1.1 File类
在介绍真正用于在流中读写数据的类之前,需要先介绍一个实用类库工具,它可以帮助我们处理文件目录问题。
File类这个名字有一定误导性,它不仅能够代表一个特定文件的名称,又能代表一个目录下一组文件的名称。
假设我们想查看一个目录列表,可以用两种方法来使用File对象。如果我们调用不带参数的list()方法,便可以获得此File对象包含的全部列表;如果我们想获得一个受限列表,例如,想得到所有扩展名为.java的文件,那么就要用到“目录过滤器”。
下面是一个简单的示例:
该示例通过list()参数的匿名内部类实现了FilenameFilter接口,请注意FilenameFilter的接口是多么简单:
accept()方法必须接受一个代表某个特定文件所在目录的File对象,以及包含了那个文件名的一个String,list()方法会为此目录对象下的每个文件名调用accept(),来判断该文件是否包含在内;判断结果由accept()返回的布尔值表示。
在示例方法中,accept()会使用一个正则表达式来匹配文件的名字,通过使用accept()会返回一个数组。
1.2绝对路径和相对路径
在上述getFileList()函数中,参数String filePath表示文件的路径,在java中,有绝对路径和相对路径两种表示方式。
绝对路径是指书写文件的完整路径,例如d:\java\Hello.java,该路径中包含文件的完整路径d:\java以及文件的全名Hello.java。使用该路径可以唯一的找到一个文件,不会产生歧义。但是使用绝对路径在表示文件时,受到的限制很大,且不能在不同的操作系统下运行,因为不同操作系统下绝对路径的表达形式存在不同。
相对路径是指书写文件的部分路径,例如\test\Hello.java,该路径中只包含文件的部分路径\test和文件的全名Hello.java,部分路径是指当前路径下的子路径,例如当前程序在d:\abc下运行,则该文件的完整路径就是d:\abc\test。使用这种形式,可以更加通用的代表文件的位置,使得文件路径产生一定的灵活性。
在Eclipse项目中运行程序时,当前路径是项目的根目录,例如工作空间存储在d:\javaproject,当前项目名称是Test,则当前路径是:d:\javaproject\Test。在控制台下面运行程序时,当前路径是class文件所在的目录,如果class文件包含包名,则以该class文件最顶层的包名作为当前路径。
另外在Java语言的代码内部书写文件路径时,需要注意大小写,大小写需要保持一致,路径中的文件夹名称区分大小写。由于’\’是Java语言中的特殊字符,所以在代码内部书写文件路径时,例如代表“c:\test\java\Hello.java”时,需要书写成“c:\\test\\java\\Hello.java”或“c:/test/java/Hello.java”,这些都需要在代码中注意。
1.3读取文件
将文件中的数据读入程序,是将程序外部的数据传入程序中,应该使用输入流InputStream或Reader。而由于读取的是特定的数据源(文件),则可以使用输入对应的子类FileInputStream或FileReader实现。
在实际书写代码时,需要首先熟悉读取文件在程序中实现的过程。在Java语言的IO编程中,读取文件是分两个步骤:1、将文件中的数据转换为流,2、读取流内部的数据。其中第一个步骤由系统完成,只需要创建对应的流对象即可,对象创建完成以后步骤1就完成了,第二个步骤使用输入流对象中的read方法即可实现了。
使用输入流进行编程时,代码一般分为3个部分:
l 创建流对象
l 读取流对象内部的数据
l 关闭流对象。
具体流程如下图所示:
图 读取文件流程图
当然,在读取文件时,也可以使用Reader类的子类FileReader进行实现,在编写代码时,只需要将上面示例代码中的byte数组替换成char数组即可。
使用FileReader读取文件时,是按照char为单位进行读取的,所以更适合于文本文件的读取,而对于二进制文件或自定义格式的文件来说,还是使用FileInputStream进行读取,方便对于读取到的数据进行解析和操作。
读取其它数据源的操作和读取文件类似,最大的区别在于建立流对象时选择的类不同,而流对象一旦建立,则基本的读取方法是一样,如果只使用最基本的read方法进行读取,则使用基本上是一致的。这也是IO类设计的初衷,使得对于流对象的操作保持一致,简化IO类使用的难度。
1.4输出文件
基本的输出流包含OutputStream和Writer两个,区别是OutputStream体系中的类(也就是OutputStream的子类)是按照字节写入的,而Writer体系中的类(也就是Writer的子类)是按照字符写入的。
使用输出流进行编程的步骤是:
l 建立输出流:建立对应的输出流对象,也就是完成由流对象到外部数据源之间的转换。
l 向流中写入数据:将需要输出的数据,调用对应的write方法写入到流对象中。
l 关闭输出流:在写入完毕以后,调用流对象的close方法关闭输出流,释放资源。
在使用输出流向外部输出数据时,程序员只需要将数据写入流对象即可,底层的API实现将流对象中的内容写入外部数据源,这个写入的过程对于程序员来说是透明的,不需要专门书写代码实现。
在向文件中输出数据,也就是写文件时,使用对应的文件输出流,包括FileOutputStream和FileWriter两个类,下面以FileOutputStream为例子说明输出流的使用。
图 输出文件流程图
在实际写入文件时,有两种写入文件的方式:覆盖和追加。其中“覆盖”是指清除原文件的内容,写入新的内容,默认采用该种形式写文件,“追加”是指在已有文件的末尾写入内容,保留原来的文件内容,例如写日志文件时,一般采用追加。在实际使用时可以根据需要采用适合的形式,可以使用:
public FileOutputStream(String name,boolean append)
只需要使用该构造方法在构造FileOutputStream对象时,将第二个参数append的值设置为true即可。
其实,所有的数据文件,包括图片文件、声音文件等等,都是以一定的数据格式存储数据的,在保存该文件时,将需要保存的数据按照该文件的数据格式依次写入即可,而在打开该文件时,将读取到的数据按照该文件的格式解析成对应的逻辑即可。
最后,在数据写入到流内部以后,如果需要立即将写入流内部的数据强制输出到外部的数据源,则可以使用流对象的flush方法实现。如果不需要强制输出,则只需要在写入结束以后,关闭流对象即可。在关闭流对象时,系统首先将流中未输出到数据源中的数据强制输出,然后再释放该流对象占用的内存空间。
使用FileWriter写入文件时,步骤和创建流对象的操作都和该示例代码一致,只是在转换数据时,需要将写入的数据转换为char数组,对于字符串来说,可以使用String中的toCharArray方法实现转换,然后按照文件格式写入数据即可。
对于其它类型的字节输出流/字符输出流来说,只是在逻辑上连接不同的数据源,在创建对象的代码上会存在一定的不同,但是一旦流对象创建完成以后,基本的写入方法都是write方法,也需要首先将需要写入的数据按照一定的格式转换为对应的byte数组/char数组,然后依次写入即可。