260 likes | 433 Views
第6章 Java 的输入输出( I/O) 处理. 本章提要: I/O 流的概念,顺序输入输出,随机输入输出,字符流,对象的串行化. 6.1 Java 输入输出流的特点和层次结构 6.1.1 Java 输入输出流概述
E N D
第6章 Java的输入输出(I/O)处理 本章提要:I/O流的概念,顺序输入输出,随机输入输出,字符流,对象的串行化
6.1 Java输入输出流的特点和层次结构 6.1.1 Java 输入输出流概述 Java中提供了java.io包来完成I/O操作,在Java1.1之前,java.io包中只能处理以byte为基本处理单位的字节流,而对于当前应用越来越广泛的16位Unicode码没有提供直接的支持,版本升级到1.1后,I/O类库的整体设计有了很大的变化,加入了以Reader和Writer为父类派生的一系列类来完成专门的字符串处理。Java中还可以对对象进行串行化,将一个对象转换为特殊处理过的字节,把这个对象保存到文件中,以备恢复为原来的对象或通过网络传输这个对象,在网络的另一个点上恢复这个对象。 6.1.2 Java中输入输出流的层次结构 Java中的所有I/O类可以分为输入和输出两个部分,InputStream 是所有输入流的父类,它提供了所有其子类共用的一些接口来统一基本的读写操作,包括从流中读取数据,在一个流中坐标记,得到一个流中的数据量参数和重新设置流中的读写位置等,其他子类在其基础上添加了特殊的方法以满足不同的读写要求。同样OutputStream是所有输出流的父类,在类OutputStream中定义了向流中写入数据及将流中数据清空等方法。
下面是类InputStream和类OutputStream的一些重要子类及功能下面是类InputStream和类OutputStream的一些重要子类及功能 (1) FileInputStream 和 FileOutputStream 用于从本地文件中读/写数据 (2) PipedInputStream 和 PipedOutputStream用于实现管道输入/输出 (3) ByteArrayInputStream 和 ByteArrayOutputStream 用于通过内存读/写数据 (4) StringBufferInputStream 用来将一个String转换为InputStream进行读取。 (5) SequenceInputStream 用来把两个或更多的InputStream输入流对象转换为单个InputStream输入流对象使用。 (6) ObjectInputStream 和 ObjectOutputStream用来直接进行对象的读写。 (7) LineNumberInputStream 可以记录数据中开始的一个字节。 (8) PushbackInputStream可以预读数据中开始的一个字节。 (9) PrintStream中提供了方便的格式输出方法。
另外,类File用来描述本地文件系统中的文件或目录,类RandomAccessFile用来对一个本地文件进行随机读写,它实现了DataInput和DataOutput接口,因此,可以用与平台无关的格式读/写Java中的基本数据。类DataInputStream、DataOutputStream同样分别实现了DataInput和DataOutput接口,用来对Java的内构类型(boolean, int, float等)提供支持。接口ObjectInput 和 ObjectOutput 提供了对对象直接读/写的一组方法,类ObjectInputStream 和 ObjectOutputStream 分别实现了这两个接口。接口FileNameFilter主要用于实现文件查找模式的匹配。 6.2 文件的顺序输入输出 Java中的所有有关顺序输入的类都是有InputStream继承的;同样的,所有有关顺序输出的类都由OutputStream继承。InputStream和OutputStream都是抽象类,分别提供了输入和输出处理的基本接口。因为InputStream和OutputStream是抽象类,仅部分实现了接口中的某些方法,所以不能直接生成对象,要通过全部实现接口的继承类来生成程序中所需要的对象,继承类中的一般都将这些基本方法重写,以提高效率或为了适应特殊的流的需要。
6.2.1 InputStream 和 OutputStream 类InputStream和类OutputStream中的主要方法: 1. 类InputStream (1) int read() 方法 int read()方法的用途是从输入流中读取下一个字节流数据,其返回值是0~255之间的一个整数,该方法是abstract的,因此子类必须实现它才能生成对象。 下面两个方法为read方法重载: 1) int read(byte b[]) 从输入流中读取长度为b.length的数据,写入字节数组b, 返回本次读取的字节数。 2) int read(byte b[], int off, int len)从输入流中读取长度为len的数据,写入字节数组b中从下标off开始的位置,返回本次读取的字节数。 (2) int available() 返回该输入流中可以读取的字节数 (3) long skip(long n) 将输入流中当前读取的位置向后移动n字节,并返回实际跳过的字节数。
(4) void close() 调用这个函数可以关闭流并且释放流的系统资源,通常系统对流对象进行垃圾收集时会自动调用这个函数,但显示调用它是个良好的编程习惯。 (5) void reset() 重置输入流中读取位置到刚调用的mark()方法所标记的位置。 (6) void mark(int readlimit)在输入流的当前读取位置做标记。从该位置开始读取readlimit所指定的数据后,所作的标记失效。 (7) Boolean markSupport() 确定该系统中的流是否支持方法mark()和reset(),在调用mark()方法和reset()方法前应用此函数判断文件系统是否支持这两种方法,以避免对程序造成影响。 2. 类OutputStream (1) void write(int b) 该方法为abstract, 必须被子类实现。该方法用来将指定的字节b作为数据写入输出流。 (2) flush() 清空输出流,并输出所有被缓冲的字节。 (3) close() 关闭流的使用方法及注意,与InputStream中的close()方法一样。
6.2.2 FileInputStream 和 FileOutputStream 类FileInputStream 和 类 FileOutputStream 分别直接继承于InputStream 和OutputStream, 它们重写或实现了父类中的所有方法,通过这两个类可以打开本地机器的文件,进行顺序的读写。 1. FileInputStream 类 FileInputStream的构造函数可以由一个String对象或File对象初始化,如下: FileInputStream oFIS = new FileInputStream(“./test1.txt”); 或 File of = new File(“./test1.txt”); FileInputStream oFIS = new FileInputStream(of); 在初始化FileInputStream对象时,如果指定路径的文件不存在,将生成一个FileNotFoundException,这个非运行时例外,必须捕捉或声明抛弃,否则编译会出错。类FileInputStream重写了父类InputStream中的方法read()、available()和close()不支持方法mark()和reset()。例6.1 readFileTest.java
2、FileOutputStream 该类的构造方法和FileInputStream一样,该类重写了父类OutputStream中的write()方法和close()方法。 在构造FileOutputStream对象时,如果指定路径的文件不存在,会自动创建一个新的文件,如果原来的文件存在,则本次写入的内容会覆盖原来文件的内容。例6.2 CopyTest.java 6.2.3 过滤流 类FilterInputStream和类FilterOutputStream分别直接从InputSteam和OutputStream继承,和它们的父类一样都是抽象类,为输入输出流提供了一个通用的接口,其本身并不能生成任何对象的实例。 过滤流在读/写数据的同时可以对数据进行处理,这些处理包括:数据的缓冲、行的计数、直接读写Java的内构类型(Built-in types):boolean, int, float等格式、预读一个字节的数据等。为了满足这些特定的需要,类FilterInputStream和类FilterOutputStream分别重写了父类的所有方法。而且它提供了对象的同步机制,使得在某一时刻只有一个线程可以访问一个I/O流,防止许多线程同时对一个I/O流进行操作所带来的严重后果。
使用过滤流时必须将此过滤流连接到某个输入/输出流上,如要生成FileInputStream oFIS = new FileInputStream(“test”); BufferedInputStream oBIS = new BufferedInputStream(oFIS); 以下类实现了FilterInputStream。 BufferedInputStream LineNumberInputSteam PushbackInputStream DataInputStream 以下类实现了FilterOutputStream DataOutputStream BufferedOutputStream PrintStream
1、 BufferedInputStream和BufferedOutputStream 类BufferedInputStream 和 BufferedOutputStream实现了带缓冲的过滤流,可以捆绑输入输出流以获得性能上的提高,在反复操作一个输入输出流时,可以避免重复连接对象。初始化BufferedInputStream和 BufferedOutputStream时,除要指定所连接的输入/输出流外,还可以指定缓冲区的大小。构造函数中缺省的缓冲区大小为32字节。 通过BufferedInputStream读取数据时,第一次读取时数据按块读入缓冲区,此后如果有读操作则直接访问缓冲区。缓冲区的作用除了可以提高可读性能外,还可以让BufferedInputStream支持mark()、reset()、skip()等方法。 通过BufferedOutputStream写数据时,数据不直接写入输出流,而是先写入缓冲区,当缓冲区的数据满时,数据才会写入BufferedOutputStream所连接的输出流,当缓冲区未满时,可以用该类提高的方法flush()将缓冲区的数据强制全部写入输出流。 2、LineNumberInputStream 该类除了支持FilterInputStream的基本方法外,还可以记录当前的行号。其计算的行号从0开始,每遇到一个换行字符,行号自动加1。输入流中的每个换行字符‘\n’、会车字符‘\r’,或者‘\r\n’,都被处理为
一个换行字符‘\n’。这在处理文本文件时是很方便的,但如果文件为二进制数据时,就没有太大前途。一个换行字符‘\n’。这在处理文本文件时是很方便的,但如果文件为二进制数据时,就没有太大前途。 3、DataInputStream和 DataOutputStream DataInputStream和DataOutputStream不同于上面的流,不但支持 InputStream和OutputStream中的那些方法,以普通方式读写二进制数据,而且还可以直接读写Java中的各种内构类型:boolean、int、float等。 4、PushbackInputStream 在通过FilterInputStream类的read()方法读取数据时,每读入一个字节,当前读取位置就向后移动一个字节。在一些情况下,我们往往需要先读入一个字节以判断输入流的一些属性,然后在正式读取时还要将全部内容读出,直接用FileInputStream类就很不方便,类PushbackInputStream提供了一个方法unread(),可以把刚读过的一个字节退回到输入流中去。例6.3 FilterTest.java
6.2.4 以其他常用的顺序方式输入输出流 1、管道流 管道是一种数据流的形式,我们可以利用管道在程序、线程、代码块之间直接传输数据。在java.io包中,类PipeInputStream描述了管道的输入,PipedOutputStream描述了管道的输出。将一个线程(程序、代码块)中的管道输出流连接到另一个线程(程序、代码块)的管道输入流中,就可以传输管道中的数据了。方法如下: PipedOutputStream out = new PipedOutputStream(); PipedInputStream in = new PipedInputStream(out); 构造了一个管道输出流,省略的代码段中将数据写入该输出流,然后该管道输出流域一个管道输入流连接起来,就可以利用该输入流中的方法读取数据了。 如例6.4 注意连接管道流的时候用的是管道输出流中的connect()方法而不是上面的构造函数连接方法,这两个方法的使用效果是一样的。
2、内存的流操作 C语言可以直接操作内存,而Java中为了保证安全性而禁止直接操作内存,但它提供了ByteArrayInputStream、ByteArrayOutputStream类来利用内存。通过ByteArrayInputStream类可以将存放在数组中的数据以流方式写入内存中暂时保存。由于这两个类都是直接继承于InputStream和OutputStream接口,因此读写方法和它顺序流差不多。例6.5 testRam.java将字符串s读到内存中保存,然后再从内存中读出提示。 3、顺序输入流 类SequenceInputStream可以把几个输入流合并为一个输入流,如果我们想把多个文件的内容一次读进内存可以利用这个类完成。例6.6 testSequence.java 6.3 文件的随机访问 Java中的RandomAccessFile类提供了随机读写文件的功能。随机读写的一个应用是对含有许多记录的文件进行操作,该类可以让我们从一条记录随意跳转到另外一条记录进行读取或修改。
类RandomAccessFile直接从Object继承,从类层次图中可以看出它们不属于InputStream或OutputStream,但由于实现了DataInput和DataOutput接口而与它们的子类DataInputStream和DataOutputStream联系起来。类RandomAccessFile直接从Object继承,从类层次图中可以看出它们不属于InputStream或OutputStream,但由于实现了DataInput和DataOutput接口而与它们的子类DataInputStream和DataOutputStream联系起来。 下面介绍类RandomAccessFile中的主要方法: 1、构造函数 生成一个RandomAccessFile对象时,首先要指定文件对象或文件名,然后指定所需要访问模式,如果以只读方式构造一个随机访问,如下: File of = new File(“testRandom”); RandomAccessFile oRAF = new RandomAccessFile(of, “r”); 如果既要读取又要修改文件,构造方法如下: RandomAccessFile oRAF = new RandomAccessFile(of, “rw”); 2、读取数据的方法 readBoolean()、readInt()、readLine()、readFully()等 3、写数据的方法 writeChar()、writeDouble()、write()等 2和3中的方法大多与DataInputStream和DataOutputStream中的用法相同。
4、指针控制方法 long getFilePointer(); 得到当前的文件读取指针 void seek(long pos); 移动文件读取指针到指定位置 int skipBytes(int n); 将文件读取指针向后移动n个字节 例6.7 testRandom.java, 此例首先向随机访问的文件写10个Double类型的数据,然后修改其中第5个,这两个过程都是以读写方式打开文件的;最后从该文件中读出所有数据并输出。 6.4 File类 Java中的File对象提供了与具体平台无关的方式来描述文件及目录对象的属性。Java把目录看作一种特殊的文件(文件列表)。 通过类File所提供的方法,可以得到文件或目录的描述信息,包括名称、所在路径、可读性、可写性、长度等,还可以生成新的目录、改变文件名、删除文件、列出一个目录中所有的文件或于某个模式相匹配的文件等等。
下面是File类中的主要方法: 1、文件生成 File类提供了三种构造方法: public File(String path); public File(String path, String name); public File(String parentdir, String childdirname) 2、获取文件的名称、路径等 String getName(); 得到一个文件名 String getPath(); 得到一个文件的路径名 String getAbsolutePath(); 得到一个文件的绝对路径名 String getParent(); 得到一个文件的上一级目录名 String renameTo(File newName); 将当前文件更名为给定路径的新文件名 3、测试文件的属性 boolean exists(); 检查File文件是否存在 boolean canWrite(); 返回当前文件是否可写 boolean canRead(); 返回当前文件是否可读 boolean isFile(); 返回当前File对象是否时文件 boolean isDirectory(); 返回当前File对象是否是目录
4、取文件信息,文件处理工具 long lastModified(); 得到文件最近一次修改的时间,改时间是相对于某一时刻的相对时间 long length(); 得到以字节为单位的文件长度 boolean delete(); 删除当前文件 5、目录操作 boolean mkdir(); 根据File对象构造函数生成一个指定的路径目录 String[] list(); 列出当前目录下的文件 例6.8 testFile.java 此例中我们先输出了File类的几个静态变量来获取文件系统的一些信息,其中pathSeparator、pathSeparatorChar是和系统相关的不同路径间的分隔符,如“/test1/file1:/test2/file2”, separator、separatorChar是和系统相关的路径分隔符或字符串,如windows系统为“\”,而Unix系统为“/”。 下例6.9 testDir.java是利用File类对目录进行操作,程序输出了当前目录下,文件名中含有特定字符串的所有文件列表。
6.5 字符流 在Java1.1之前,I/O流只支持8位字节流,要转换为字符需要另外的转换方式,16位的Unicode(Java中的char类型就是16位的Unicode字符)字符越来越广泛使用,应用旧的IO类库很不方便,因此,从Java1.1开始的I/O类库,作了一些重大的改进,加入了专门处理字符流的类,使Java语言对字符流的处理更加方便,新的类库对速度也进行了优化,运行更快。 这里呢,我们将介绍新类库中专门用来进行字符流处理的,以Reader和Writer为基类的一些常用派生类。 6.5.1 基类Reader和Writer 类似于旧类库中的InputStream和OutputStream两个抽象基类,新类库中的所有字符流处理的类都基于Reader和Writer这两个类,他们也是抽象类,只是提供了一些用于字符流处理的接口,本身不能用来生成实例。
1、类Reader 该类是所有字符流输入类的父类,该类的主要方法如下: (1)、读字符 public int read() throws IOException 读取一个字符 public int read(char cbuf[]) throws IOException 读取一系列字符到数组cbuf[]中 public abstract int read(char cbuf[], int off, int len)throws IOException 读取len个字符到数组cbuf[]中,开始位置在下标off,该方法必须由子类实现。 (2)、标记流及判断是否支持标记 public boolean markSupported()判断当前流是否支持标记的mark()等方法 public void mark(int readAheadLimit)throw IOException给当前流作标记,最多可以向前回溯readAheadLimit个字符 public void reset()throws IOException将当前流重置到上一次作标记处。
(3)、关闭流 public abstract void close()throws IOException 该方法必须被子类实现。一个流关闭之后,再对其进行read()、ready()、mark()、reset()等操作,会产生IOException。对一个已经关闭的流再进行close()操作,不会产生任何效果。 2、类Writer 该类是索引字符流输出类的父类,该类的主要方法如下: (1)、写入字符 public void write(int c)throws IOException 将整数类型的低16位写入输出流 public void write(char cbuf[])throws IOException 将字符数组cbuf[]中的字符写入输出流 public abstract void write(char cbuf[],int off, int len)throws IOException 将字符数组cbuf[]中,开始位置为下标off的连续len个字符写入输出流 public void write(String str)throws IOException 将字符串str中的字符写入输出流 public void write(String str, int off, int len)throws IOException 将字符串str中,从下标off开始的len个字符写入输出流
(2)、flush() 清空输出流,强制输出所有被缓冲的字符 (3)、关闭流 public abstract void close()throws IOException 该方法使用时的注意事项和类Reader的close()方法相同。 6.5.2 字符流I/O中的重要子类 1、类InputStreamReader和类OutputStreamWriter (1)、构造函数 public InputStreamReader(InputStream in) 用InputStream类的字节输入流构造一个字符输入流。这里字节流的编码规范与具体的平台有关,构造为一个字符流后与平台无关。 public InputStreamReader(InputStream in, String enc)throws UnsupportedEncodingException 同样是基于字节流构造一个输入字符流,但使用的是指定的编码规范,如果编码规范非法,将产生UnsupportedEncodingException例外。
(2)、读取和写入数据 与Reader和Writer类中的方法基本相同 (3)、获取当前编码方式 public String getEncoding() (4)、关闭流 public void close()throws IOException 2、类BufferedReader和BufferedWriter (1)、构造函数 public BufferedReader(Reader in) 基于一个普通字符输入流,构造一个字符缓冲流 public BufferedReader(Reader in, int sz) 基于一个普通字符输出流,构造一个字符缓冲流,缓冲区的大小设定为sz。 public BufferedWriter(Writer out) 基于一个普通字符输出流,构造一个字符缓冲输出流。 public BufferedWriter(Writer out, int sz) 基于一个普通字符输出流,构造一个字符输出流,缓冲区的大小设定为sz。
(2)、读写字符 除了Reader和Writer中提供的基本读写方法外,还有对整行字符的处理 public String readLine()throws IOException 向字符输出流中写入一个行结束标记,该标记不是简单的换行符“\n”,而是系统定义的line.separator 6.5.3 新旧类库的对应关系 由于设计上的一些限制,我们有时需要使用Java1.0的I/O类库(旧类库),但应该尽量是用新类库。新旧类库的结构在设计上是很相似的。Java新旧类库对应关系表6-1 6.6 对象的串行化 6.6.1 什么是对象串行化 在Java 程序中,一般情况下我们创建的对象随程序的终止而消失。但是有些时候,我们希望把创建的某些对象完整的保留下来,以后再次使用。我们希望把某个类的一个实例保存在本地硬盘上,供以后的程序使用,或保存在网络上的一台远程主机上,把这个对象提供给那台主机中的程序使用,这可以为许多程序提供很多的方便。
Java中可以通过对象的串行化来实现这个功能。串行化是只对象通过把自己转化为一系列字节,记录字节的状态数据,以便再次利用的这个程序。Java中可以通过对象的串行化来实现这个功能。串行化是只对象通过把自己转化为一系列字节,记录字节的状态数据,以便再次利用的这个程序。 6.6.2 如何进行串行化操作 Java 1.1以后添加了对象串行化的机制,可以把实现了Serializable接口的对象串行化。Serializable接口中没有定义任何方法,只是一个特殊的标记,用来告诉Java编译器,这个对象参加了串行化的协议,可以把它串行化。因此一个类实现了Serializable接口时,并不需要实现任何针对该接口的方法。例6.10 testSerialization.java 6.6.3 对象串行化中的一些问题 1、串行化过程能保存什么 串行化过程只能保存对象的非静态成员变量,不能保存静态的成员变量。该过程保存的只是成员变量的值,而没有保存任何变量的修饰符。对于类方法,由于没有状态量,串行化过程与之无关。
2、某些对象不能串行化 由于保密性的要求,应该用transient关键字,或用Externalizable声明对象。不是所有的类对象都可以串行化,比如Thread对象、FileInputStream对象,这些对象的状态是瞬时的,因而使该对象的串行化过程无法进行。如果这些类出现在串行化对象中,要用transient关键字修饰,否则编译程序将报错。 另外,我们也可能觉得在可以串行化的对象中,某些子对象包含了一些敏感信息(如密码等数据)需要保密,串行化可能将该信息保存在磁盘上或网络的其他主机上,因此我们不想让这样的数据参与串行化。这些不参加串行化的子对象的前面也要加上transient。 另一种避免这种自动保留所有子对象的方法是用Externalizable接口来代替serilization接口,该接口不能自动用串行化后的数据,恢复对象成员变量.例6.11 extObj.java 3、串行化过程中的定制 一般使用缺省得串行化时,首先向文件中写入类数据和类字段的信息,然后按照名称的上升排序将子对象的数值写入。但有时我们想根据自己的愿望来控制串行化过程,这时就需要重写serializable接口中的writeObject()方法和readObject()方法。
4、ClassNotFoundException问题 如果在本地或网络上串行化一个类,必须保证在恢复时Java虚拟机可以找到这个类的.class文件,在本地路径中或在网络的其他确切地址。否则将会出现ClassNotFoundException 练习:编写一个程序,将一段文字以顺序读写方式写入文件,然后读出并显示在屏幕上。 文字如下:I am a big big girl in a big big world, it’s not a big big thing, if you leave me. But I do do feel, I miss you much…