第6章 Java 的输入输出( I/O) 处理

26
6 6 Java Java 第第第第第 第第第第第 ( ( I/O) I/O) 第第 第第 第第第第第第第第I/O I/O 第第第第 第第第第第第第 第第第第 ,, 第第第第 第第第第第第第 第第第第 ,, 第第 第第第第 第第第第第第 ,, 第第 第第第第 第第第第第第 ,,

description

第6章 Java 的输入输出( I/O) 处理. 本章提要: I/O 流的概念,顺序输入输出,随机输入输出,字符流,对象的串行化. 6.1 Java 输入输出流的特点和层次结构 6.1.1 Java 输入输出流概述 - PowerPoint PPT Presentation

Transcript of 第6章 Java 的输入输出( I/O) 处理

Page 1: 第6章  Java 的输入输出( I/O) 处理

第第 66 章 章 JavaJava 的输入输出的输入输出((I/O)I/O) 处理处理

本章提要:本章提要: I/OI/O 流的概念,顺序输入流的概念,顺序输入输出,随机输入输出,字符流,对象输出,随机输入输出,字符流,对象的串行化的串行化

Page 2: 第6章  Java 的输入输出( 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 中定义了向流中写入数据及将流中数据清空等方法。

Page 3: 第6章  Java 的输入输出( I/O) 处理

下面是类 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 中提供了方便的格式输出方法。

Page 4: 第6章  Java 的输入输出( I/O) 处理

另外,类 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 是抽象类,仅部分实现了接口中的某些方法,所以不能直接生成对象,要通过全部实现接口的继承类来生成程序中所需要的对象,继承类中的一般都将这些基本方法重写,以提高效率或为了适应特殊的流的需要。

Page 5: 第6章  Java 的输入输出( I/O) 处理

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 字节,并返回实际跳过的字节数。

Page 6: 第6章  Java 的输入输出( I/O) 处理

(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() 方法一样。

Page 7: 第6章  Java 的输入输出( I/O) 处理

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

Page 8: 第6章  Java 的输入输出( I/O) 处理

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 流进行操作所带来的严重后果。

Page 9: 第6章  Java 的输入输出( I/O) 处理

使用过滤流时必须将此过滤流连接到某个输入 / 输出流上,如要生成FileInputStream oFIS = new FileInputStream(“test”);BufferedInputStream oBIS = new BufferedInputStream(oFIS);

以下类实现了 FilterInputStream 。BufferedInputStreamLineNumberInputSteamPushbackInputStreamDataInputStream

以下类实现了 FilterOutputStreamDataOutputStreamBufferedOutputStreamPrintStream

Page 10: 第6章  Java 的输入输出( I/O) 处理

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’, 都被处理为

Page 11: 第6章  Java 的输入输出( I/O) 处理

一个换行字符‘ \n’ 。这在处理文本文件时是很方便的,但如果文件为二进制数据时,就没有太大前途。3 、 DataInputStream 和 DataOutputStream

DataInputStream 和 DataOutputStream 不同于上面的流,不但支持 InputStream 和 OutputStream 中的那些方法,以普通方式读写二进制数据,而且还可以直接读写 Java 中的各种内构类型:boolean 、 int 、 float 等。

4 、 PushbackInputStream在通过 FilterInputStream 类的 read() 方法读取数据时,每读入一个字节,当前读取位置就向后移动一个字节。在一些情况下,我们往往需要先读入一个字节以判断输入流的一些属性,然后在正式读取时还要将全部内容读出,直接用 FileInputStream 类就很不方便,类PushbackInputStream 提供了一个方法 unread(), 可以把刚读过的一个字节退回到输入流中去。例 6.3 FilterTest.java

Page 12: 第6章  Java 的输入输出( I/O) 处理

6.2.4 以其他常用的顺序方式输入输出流1、管道流管道是一种数据流的形式,我们可以利用管道在程序、线程、代码块之间直接传输数据。在 java.io 包中,类 PipeInputStream 描述了管道的输入, PipedOutputStream 描述了管道的输出。将一个线程( 程序、代码块 ) 中的管道输出流连接到另一个线程 ( 程序、代码块 )的管道输入流中,就可以传输管道中的数据了。方法如下:PipedOutputStream out = new PipedOutputStream();PipedInputStream in = new PipedInputStream(out);构造了一个管道输出流,省略的代码段中将数据写入该输出流,然后该管道输出流域一个管道输入流连接起来,就可以利用该输入流中的方法读取数据了。 如例 6.4 注意连接管道流的时候用的是管道输出流中的 connect()方法而不是上面的构造函数连接方法,这两个方法的使用效果是一样的。

Page 13: 第6章  Java 的输入输出( I/O) 处理

2、内存的流操作C 语言可以直接操作内存,而 Java 中为了保证安全性而禁止直接操作内存,但它提供了ByteArrayInputStream 、 ByteArrayOutputStream 类来利用内存。通过 ByteArrayInputStream 类可以将存放在数组中的数据以流方式写入内存中暂时保存。由于这两个类都是直接继承于 InputStream 和OutputStream 接口,因此读写方法和它顺序流差不多。例 6.5 testRam.java 将字符串 s读到内存中保存,然后再从内存中读出提示。

3、顺序输入流类 SequenceInputStream 可以把几个输入流合并为一个输入流,如果我们想把多个文件的内容一次读进内存可以利用这个类完成。例 6.6 testSequence.java

6.3 文件的随机访问Java 中的 RandomAccessFile 类提供了随机读写文件的功能。随机读写的一个应用是对含有许多记录的文件进行操作,该类可以让我们从一条记录随意跳转到另外一条记录进行读取或修改。

Page 14: 第6章  Java 的输入输出( I/O) 处理

类 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 中的用法相同。

Page 15: 第6章  Java 的输入输出( I/O) 处理

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 所提供的方法,可以得到文件或目录的描述信息,包括名称、所在路径、可读性、可写性、长度等,还可以生成新的目录、改变文件名、删除文件、列出一个目录中所有的文件或于某个模式相匹配的文件等等。

Page 16: 第6章  Java 的输入输出( I/O) 处理

下面是 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 对象是否是目录

Page 17: 第6章  Java 的输入输出( I/O) 处理

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 类对目录进行操作,程序输出了当前目录下,文件名中含有特定字符串的所有文件列表。

Page 18: 第6章  Java 的输入输出( I/O) 处理

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 这两个类,他们也是抽象类,只是提供了一些用于字符流处理的接口,本身不能用来生成实例。

Page 19: 第6章  Java 的输入输出( I/O) 处理

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 将当前流重置到上一次作标记处。

Page 20: 第6章  Java 的输入输出( I/O) 处理

(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 个字符写入输出流

Page 21: 第6章  Java 的输入输出( I/O) 处理

(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例外。

Page 22: 第6章  Java 的输入输出( I/O) 处理

(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 。

Page 23: 第6章  Java 的输入输出( I/O) 处理

(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 程序中,一般情况下我们创建的对象随程序的终止而消失。但是有些时候,我们希望把创建的某些对象完整的保留下来,以后再次使用。我们希望把某个类的一个实例保存在本地硬盘上,供以后的程序使用,或保存在网络上的一台远程主机上,把这个对象提供给那台主机中的程序使用,这可以为许多程序提供很多的方便。

Page 24: 第6章  Java 的输入输出( I/O) 处理

Java 中可以通过对象的串行化来实现这个功能。串行化是只对象通过把自己转化为一系列字节,记录字节的状态数据,以便再次利用的这个程序。6.6.2 如何进行串行化操作

Java 1.1 以后添加了对象串行化的机制,可以把实现了Serializable 接口的对象串行化。 Serializable 接口中没有定义任何方法,只是一个特殊的标记,用来告诉 Java编译器,这个对象参加了串行化的协议,可以把它串行化。因此一个类实现了Serializable 接口时,并不需要实现任何针对该接口的方法。例 6.10 testSerialization.java

6.6.3 对象串行化中的一些问题1、串行化过程能保存什么串行化过程只能保存对象的非静态成员变量,不能保存静态的成员变量。该过程保存的只是成员变量的值,而没有保存任何变量的修饰符。对于类方法,由于没有状态量,串行化过程与之无关。

Page 25: 第6章  Java 的输入输出( I/O) 处理

2、某些对象不能串行化由于保密性的要求,应该用 transient 关键字,或用Externalizable声明对象。不是所有的类对象都可以串行化,比如Thread 对象、 FileInputStream 对象,这些对象的状态是瞬时的,因而使该对象的串行化过程无法进行。如果这些类出现在串行化对象中,要用 transient 关键字修饰,否则编译程序将报错。另外,我们也可能觉得在可以串行化的对象中,某些子对象包含了一些敏感信息 (如密码等数据 ) 需要保密,串行化可能将该信息保存在磁盘上或网络的其他主机上,因此我们不想让这样的数据参与串行化。这些不参加串行化的子对象的前面也要加上 transient 。另一种避免这种自动保留所有子对象的方法是用 Externalizable 接口来代替 serilization 接口,该接口不能自动用串行化后的数据,恢复对象成员变量 .例 6.11 extObj.java3、串行化过程中的定制一般使用缺省得串行化时,首先向文件中写入类数据和类字段的信息,然后按照名称的上升排序将子对象的数值写入。但有时我们想根据自己的愿望来控制串行化过程,这时就需要重写 serializable 接口中的writeObject() 方法和 readObject() 方法。

Page 26: 第6章  Java 的输入输出( I/O) 处理

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…