本文介绍java.io包中的标准输入/输出流的概念。并运用文件流读写文本文件及二进制文件,运用对象流的序列化和反序列化,进而进行网络传输。在聊天系统的设计中综合使用它们。
本文介绍java.io包中的标准输入/输出流的概念。并运用文件流读写文本文件及二进制文件,运用对象流的序列化和反序列化,进而进行网络传输。在聊天系统的设计中综合使用它们。
当客户之间进行聊天时,有时需要将聊天信息存储为本地文件或者客户之间传输文件,而Java语言的文件操作需要通过输入输出文件流的方式进行。针对聊天信息的记录,可以将其以文本文件的格式存储,处理文本文件使用字符流。针对传输文件,将其以二进制文件的格式处理,二进制文件使用字节流。
在Java中,为了处理不同的数据或者数据存储或者数据传递,使得其编程更加统一,引入流的概念,在处理磁盘文件、内存缓冲区以及网络数据时,都可以以同样的方式进行处理。
流是一种数据通信通道,用于一系列的字节或字符以先进先出的方式流动。Java流按照传递的字节或字符的不同数据类型将其分为字节流和字符流两种类型,字节流是指通道用于流动以8位单字节为单位的二进制数据,如图字节输入流与输出流
所示:
字节输入流与输出流:
而字符流是指通道用于流动以16位双字节为单位的Unicode字符数据,如图字符输入流与输出流
所示。
字符输入流与输出流:
不管是字节流还是字符流,传递数据时,分别以输入流和输出流的方式进行,当应用程序需要读入数据时,先从数据源(文件、缓存或网络)打开输入流,然后从中顺序读取数据,完成后关闭输入流;当应用程序需要写出数据时,先向目标(文件、缓存或网络)打开输出流,然后依次写出数据,完成后关闭输出流。
在Java中,为了支持面向字节的流,Java提供了InputStream
类和OutputStream
类,它们是抽象类,分别表示字节输入流的所有类的超类和字节输出流的所有类的超类。InputStream类的继承结构如图InputStream类的继承关系
所示:
InputStream类的继承关系:
OutputStream类的继承结构如图OutputStream类的继承关系
所示:
OutputStream类的继承关系:
同样,为了支持面向字符的流,Java提供了Reader
类和Writer
类,它们也是抽象类,分别表示字符输入流的所有类的超类和字符输出流的所有类的超类。Reader类的继承结构如图Reader类的继承关系
所示:
Reader类的继承关系:
Writer类的继承结构如图Writer类的继承关系
所示。
Writer类的继承关系:
文件流是用来传输文件的内容,但对于文件名的表示,需要用一个对象来体现,这个对象可以是String、File或FileDescript类型的对象。
⑴ 运用File类进行文件描述
文件是相关的记录或数据的集合,它存储在操作系统的某个目录中,在Java中,File
类的实例代表磁盘文件的对象,File类并不是文件流类,它不能从文件读取数据或向文件写入数据,它只是描述文件对象的属性。文件在很多应用程序中是读取数据的数据源和写入数据的目的地,在用File描述文件时,包括获取文件大小、是否读写、文件路径、文件清单列表、过滤文件名、修改文件名、删除文件和新建目录等操作功能,在此,目录被作为一种文件来处理。File
类的结构如图File类结构
所示:
File类结构:
创建文件对象时,可用其构造方法进行,例如:
File file1=new File("e:\\LeadWay"); //创建目录
File file2=new File(file1,"zcj.txt"); //创建文件
关于创建File对象和其方法的使用,可以参阅Java API文档。
⑵运用FileInputStream
类与FileOutputStream
类传输文件字节流
对于二进制的图像文件,采用FileInputStream
和FileOutputStream
流类对其内容进行读取,或者将字节数据写入。
清单 10-1 将文件名为“image1.jpg”图像文件修改后另存为“image2.jpg”
1: import java.io.*;
2: public class UpdateImage {
3: public static void main(String[] args) {
4: try{
5: File file1=new File("E://Workspace//MyJava//image1.jpg");
6: File file2=new File("E://Workspace//MyJava//image2.jpg");
7: FileInputStream in=new FileInputStream(file1);
8: FileOutputStream out=new FileOutputStream(file2);
9: int temp,i=1;
10: System.out.println("读取修改图像文件...");
11: while((temp=in.read())!=-1){
12: i++;
13: if(i<=256){
14: out.write(temp);
15: continue;
16: }
17: if (i%256==0)
18: temp+=(int)(Math.random()*128);
19: if(temp>255)
20: temp%=256;
21: out.write(temp);
22: }
23: in.close();
24: out.close();
25: System.out.println("完成修改图像文件...");
26: }
27: catch(Exception ex){
28: System.out.println(ex.getMessage());
29: }
30: }
31:}
⑶运用FileReader类与FileWriter类传输文件字符流
当对文本文件的内容进行处理时,需要使用FileReader类与FileWriter类。
清单10-2 对文本文件进行复制,将zcj1.txt的内容传输到zcj2.txt中
1: import java.io.*;
2: public class CopyFile {
3: public static void main(String[] args) {
4: try{
5: File file1=new File("E://Workspace//MyJava//zcj1.txt");
6: File file2=new File("E://Workspace//MyJava//zcj2.txt");
7: FileReader in=new FileReader(file1);
8: FileWriter out=new FileWriter(file2);
9: int temp;
10: System.out.println("读取写入文本文件...");
11: while((temp=in.read())!=-1)
12: out.write(temp);
13: in.close();
14: out.close();
15: System.out.println("完成写入文本文件...");
16: }catch(Exception ex){
17: System.out.println(ex.getMessage());
18: }
19: }
20:}
⑷ 运用RandomAccessFile类实现对随机存取文件的读取和写入
Java中的流基本上是成对出现的,对数据的传递是顺序进行的,但有些时候需要在文件的指定位置读取写入数据,Java提供的RandomAccessFile
类同时具有输入流和输出流的所有功能,并且实现了DataInput
和DataOutput
接口,如图RandomAccessFile与实现接口
所示。
RandomAccessFile与实现接口
当一个包含RandomAccessFile
对象的程序操作一个文件时,程序能够在文件的指定位置以原始数据类型读取或写入数据,当写入一个int
类型的值时,四个字节的数据输出到文件;而当读取一个double
类型的值时,八个字节的数据从文件输入。
随机存取文件相当于一个字节数组,将指向该隐含数组的光标或索引称为文件指针
。这样,RandomAccessFile
类的对象就可以通过文件指针执行文件内特定单元的输入/输出操作。RandomAccessFile
也支持访问权限,允许文件以只读、只写或读写的方式被打开。
以两种方式来创建RandomAccessFile
的实例,使用路径名的字符串或使用File
类的对象作为RandomAccessFile
类构造方法的参数。
RandomAccessFile(String pathname,String mode)
RandomAccessFile(File name,String mode)
其中,参数mode用于设置对文件的访问权限(r或rw)。RandomAccessFile
类的结构如图RandomAccessFile类结构
所示:
RandomAccessFile类结构:
清单 10-3 随机读写文件
1: import java.io.*;
2: public class RandomFileDemo {
3: final void readwriteFile() {
4: int []numbers = {34, 56, 78, 43, 23, 54, 67, 32, 7, 89};
5: try {
6: File file=new File("RandomFile.dat");
7: RandomAccessFile raf = new RandomAccessFile(file, "rw");
8: for (int i = 0; i < numbers.length; i++) {
9: raf.writeInt(numbers[i]);
10: }
11: System.out.println("逆向读取给定数组的内容…");
12: for (int i = numbers.length - 1; i >= 0; i--) {
13: raf.seek(i * 4);
14: System.out.println(raf.readInt());
15: }
16: raf.close();
17: } catch (Exception ex) {
18: System.out.println("异常:" + ex.getMessage());
19: } finally {
20: System.out.println("执行最后代码...");
21: }
22: }
23: public static void main(String[] args) {
24: RandomFileDemo objRandomFile = new RandomFileDemo();
25: objRandomFile.readwriteFile();
26: }
27:}
⑸ 运用SequenceInputStream类实现连接文件
如果需要将多个输入流拼接成一个,使用SequenceInputStream
类来实现,它的构造方法使用两个InputStream
或一个InputStream
的枚举产生SequenceInputStream
的对象。
SequenceInputStream(Enumeration e)
SequenceInputStream(InputStream s1,InputStream s2)
实际运作时,首先读完第一个InputStream
,接着读第二个。如果参数是InputStream
的枚举,则依次读完作为枚举元素的所有的InputStream
。
清单 10-4 实现连接文件
1: import java.io.*;
2: import java.util.*;
3: class FilesEnumerator implements Enumeration {
4: private Enumeration files;
5: public FilesEnumerator(Vector files) {
6: this.files = files.elements();
7: }
8: public boolean hasMoreElements() {
9: return files.hasMoreElements();
10: }
11: public Object nextElement() {
12: try {
13: return new FileInputStream(files.nextElement().toString());
14: } catch (Exception ex) {
15: return null;
16: }
17: }
18:}
19:public class FilesConnection {
20: public static void main(String[] args) {
21: int temp;
22: Vector files = new Vector();
23: File file1 = new File("zcj1.txt");
24: File file2 = new File("zcj2.txt");
25: File file3 = new File("zcj3.txt");
26: files.addElement(file1);
27: files.addElement(file2);
28: files.addElement(file3);
29: FilesEnumerator fe = new FilesEnumerator(files);
30: SequenceInputStream in = new SequenceInputStream(fe);
31: try {
32: while ((temp = in.read()) != -1) {
33: System.out.print((char) temp);
34: }
35: in.close();
36: } catch (IOException ex) {
37: System.out.println(ex.getMessage());
38: }
39: }
40:}
在聊天系统中的文件传输是一个必要的功能,在此通过一个抽象类定义文件传输的基本方法,抽象类名为DushiFileTransfer
,再通过两个子类DushiClientFileTransfer
与DushiServerFileTransfer
分别用于客户端与服务器端文件传输的处理。类图设计如图文件传输设计
所示:
文件传输设计:
清单 10-5 DushiFileTransfer.java
清单 10-6 DushiClientFileTransfer.java
清单 10-7 DushiServerFileTransfer.java
尽管Java类库存在大量的输入输出流,在实际应用中需要通过多种不同的组合方式,而且经常用到的只有几种典型方式。在具体应用之前,需要了解相关的概念。
节点流: 读或者写到特定位置(如:磁盘文件、内存或网络)的输入输出流。例如,FileInputStream
和FileOutputStream
是读入或写出数据到磁盘文件的节点流;ByteArrayInputStream
和ByteArrayOutputStream
是读入或写出数据到内存的节点流;ObjectInputStream
和ObjectOutputStream
是读入或写出数据到网络的节点流。
过滤流: 有些时候为了增强流功能,需要从一个流读入数据,然后写到另一个流。例如,PrintStream为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。过滤流也称为处理流,它是建立在已存在的流之上,通过对数据的处理提供较强大的读写功能。过滤流又可细分为缓冲流、转换流、数据流、打印流、对象流等。
java.io
包中定义了如此之多的流类,需要从不同的角度对其进行分类:
对于标准键盘的输入处理,由于System.in
是InputStream
类型,直接以字节流的方式读入数据,但有时需要以字符流的方式处理数据,此时要将字节流转换为Unicode
字符流,可以使用InputStreamReader
类完成这样的功能。为了提高效率,将键盘输入的数据读到BufferedReader
类的对象中,实现缓冲功能。
清单 10-8 从键盘读入用户信息,并将其写出到文件: CustomerInfo.java
清单 10-9 IO流的典型应用: IOStreamApp.java
基于网络的聊天系统,当一个数据对象中包含多个属性信息数据时,将该对象从客户端传递到服务器端,再从服务器端将该对象传递到另一个客户端。这时数据对象在网络上传递,就会有一项基本要求,针对对象的序列化和反序列化。即对象在进行网络传输之前,对其进行适合网络传输格式的数据转换,网络传输之后,再进行将格式复原为对象的数据转换。
在Java中,类ObjectInputStream
称为对象输入流,实现了接口ObjectInput
,用于从文件或者网络输入对象;而相反,类ObjectOutputStream
称为对象输出流,实现了接口ObjectOutput
,用于将对象输出到文件或者网络。对象输入输出流与其实现的接口之间的关系如图对象流与接口关系
所示。
对象流与接口关系:
ObjectInputStream
中提供的方法readObject()
实现了对象的反序列化,而ObjectOutputStream
中提供了方法writeObject()
实现了对象的序列化。这样,利用它们就可以针对某种介质(文件
、网络
)读写对象,这种读写是将内存中的对象图映射到输入输出流中的字节流。
内存中的对象图是运行时的实例化对象及其之间的引用关系,它是一种复杂的有向图。而字节流是一种有序的字节序列,要将对象写入到输出字节流,就要建立对象图到字节流之间的映射关系,这种映射就是对象的序列化
。反之,建立字节流到对象图之间的逆向映射就是对象的反序列化
,用于从输入字节流中读出对象。
对象的序列化将使内存中的对象可以保存到外存中,或者实现对象在网络上传递,从而实现了对象的持久性。对象保存到文件或者进行网络传递,这样可以任何时候打开文件读取对象到内存,然后使用对象;或者可以在任何网络节点的主机上获取对象并使用。
⑴ 序列化方法
通过输出对象流ObjectOutputStream
中的writeObject()
方法实现对象到字节流的序列化操作。
清单 10-10 将当前的日期对象序列化后写入文件
1: import java.io.*;
2: import java.util.Date;
3: public class Serial {
4: public void write(Date date) throws IOException {
5: ObjectOutputStream out = new ObjectOutputStream(
6: new FileOutputStream("date.dat"));
7: out.writeObject(date);
8: }
9: public static void main(String[] args) {
10: Serial serial = new Serial();
11: Date date = new Date();
12: try {
13: serial.write(date) ;
14: } catch (IOException e) {
15: e.printStackTrace();
16: }
17: }
18:}
⑵ 反序列化方法
通过输入对象流ObjectInputStream
中的readObject()
方法实现字节流到对象的反序列化操作。
清单 10-11 从文件读取字节流,反序列化为日期对象后显示
1: import java.io.*;
2: import java.text.SimpleDateFormat;
3: import java.util.Date;
4: public class Deserial {
5: public Date read() throws IOException,ClassNotFoundException {
6: ObjectInputStream in = new ObjectInputStream(
7: new FileInputStream("date.dat"));
8: return (Date)in.readObject();
9: }
10: public static void main(String[] args) {
11: Deserial deserial = new Deserial();
12: SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
13: try {
14: Date date = deserial.read();
15: System.out.println(sdf.format(date));
16: } catch (IOException e) {
17: e.printStackTrace();
18: } catch (ClassNotFoundException e) {
19: e.printStackTrace();
20: }
21: }
22:}
数据类DushiMessage
的实例对象在聊天系统的客户端与服务器端的传递,定义了一个抽象类DushiConnection
,作为DushiServerConnection
与DushiClientConnection
类的超类,用于从客户机或服务器的连接。包含了对所需要的数据流开放的方法,进行数据对象的处理。类图设计如图数据对象处理
所示:
数据对象处理:
清单 10-12 DushiMessage.java
清单 10-13 DushiConnection.java
清单 10-14 DushiServerConnection.java
清单 10-15 DushiClientConnection.java
数据类的对象通常用于持久化或用于网络传递,那么需要对其对象进行序列化操作,但要求数据类定义时通过实现java.io.Serializable
接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。
transient
关键字只能修饰成员变量,而不能修饰方法和类,也不能修饰本地变量。当进行对象的序列化时,被transient
关键字修饰的变量不再能被序列化,这种情况是有时候会需要特定的对象数据在序列化时不进行处理,关掉类的特定的数据域。
在一个实体Java类中,有时也将transient
和static
修饰的属性变量称为非持久性字段,它们不能进行序列化,不能映射到数据库。
博文最后更新时间: