输入和输出处理(二)
读写二进制文件
读写二进制文件常用的类有DataInputStream和DataOutputStream。
1.使用字节流读二进制文件
利用DataInputStream类读进制文件, 其实与利用FileInputStream 类读文本文件极其相似,也要用到FileInputStream类关联二进制文件。具体操作步骤如下。
(1) 引入相关的类。
import java.io.FileInputStream;
import java.io.DataInputStream;
(2)构造一个数据输入流对象。
FileInputStream fis=new FileInputStream("HelloWorld.class");
DataInputStream dis= new DataInputStream(fis);
(3)利用数据输入流类的方法读取二进制文件中的数据。
dis.readInt();
//读取出来的是整数
dis.readByte();
//读取出来的数据是Byte类型
(4)关闭数据输入流。
dis.close();
//关闭数据输入流
2. 使用字节流写二进制文件
利用DataOutputStream类写二进制文件,其实与利用FileOutputStream类写文本文件极其相似,也要用到FileOutputStream类关联二进制文件。具体操作步骤如下。
(1)引入相关的类。
import java.io.FileOutputStream;
import java.io.DataOutputStream;
(2)构造一个数据输出流对象。
FileOutputStream outFile=new FileOutputStream("temp.class");
DataOutputStream out=new DataOutputStream(outFile);
(3)利用数据输出流类的方法把数据写入二进制文件。
out.write(1);
//把数据写入二进制文件
(4)关闭数据输出流。
out.close();
示例6
实现从一个二进制文件ReadAndWriteBinaryFile.class中读取数据,然后复制到另一二进制文件temp.class中。
实现步骤如下。
1)引入相关类。
2)创建流对象。
3)调用DatalnputStream对象的read()方法读数据。
4)调用DataOutputStream对象的write()方法写数据。
5)读取写入的数据。
6)关闭流对象。
关键代码:
//创建流对象
FileInputStream fis = new FileInputStream("C:\\myDoc\\FileMethods.class");
DataInputStream dis = new DataInputStream(fis);
FileOutputStream outFile = new FileOutputStream("C:\\myDoc\\temp.class");
DataOutputStream out = new DataOutputStream(outFile);
int temp;
while((temp=dis.read())!=-1)
{//读数据
out.write(temp);//把读取的数据写入到temp.class文件中
}
public Test() throws FileNotFoundException {
}
//关闭流
fis.close();
out.close();
DataInputStream类与DataOutputStream类搭配使用,可以按照与平台无关的方式从流中读取基本数据类型的数据,如int、float、 long、 double 和boolean等。此外,DataInputStream的readUTF()方法能读取采用UTF-8字符集编码的字符串。如下面的代码所示。
FileInputStream in1=new FileInputStream("C:\\myDoC\\hello.txt");
BufferedInputStream in2=new BufferedInputStream(in1);
DataInputStream in=new DataInputStream(in2);
System.out.println(in.readByte());
System.out.println(in.readLong());
System.out.println(in.readChar());
System.out.println(in.readUTF());
DataOutputStream类可以按照与平台无关的方式的向流中写入基本数据类型的数据,如int、float、 long、 double 和boolean等。此外,DataOutputStream的writeUTF()方法能写入采用UTF-8字符集编码的字符串。
DataOutputStream)类的所有写方法都是以write开头,如writeByte()、writeLong()
等方法。如下面的代码所示。
FileOutputStream out1=new.FileOutputStream("C:\\myDoc\\hello.txt");
BufferedOutputStream out2=new BufferedOutputStream(out1);
DataOutputStream out new DataOutputStream(out2);
out.writeByte(1);
out.writeLong(2);
out. writeChar('c');
out. writeUTF("hello");
重定向标准I/O
System.in和Sytem.out, 它们是Java提供的两个标准输入输出流,主要用于从键盘接受数据以及向屏幕输出数据。
System.in常见方法如下所示。
➢int read(),此方法从键盘接收一个字 节的数据,返回值是该字符的ASCII码。
➢int read(byte []buf),此方法从键盘接收多个字节的数据,保存至buf中,返回值是接收字节数据的个数,非ASCII码。
System.out常见方法如下所示。
➢print(),向屏幕输出数据,不换行,参数可以是Java的任意数据类型。
➢println(), 向屏幕输出数据,换行,参数可以是Java的任意数据类型。
有些时候使用标准I/O对文件进行读写很方便,那么如何使用System.in 读取文件中的数据以及使用System.out向文件中写入数据呢?首先需要重定向标准IO,简单地说,就是将标准I/O重新定向到其他的I/O设备,例如将输出设备定位到文件。
System类提供了3个重定向标准输入/输出的方法,如表3-7所示。
表3-7重 定向标准输入/输出的方法
方法 | 说明 |
---|---|
static void setErr(PrintStream err) | 重定向标准错误输出流 |
Static void setIn(InputStream in) | 重定向标准输入流 |
Static void setCut(PrintStream out) | 重定向标准输出流 |
如下代码实现了System out的重定向,运行此代码,在控制台将不输出任何内容,而是输出到文件hello.txt中。
//创建PrintStream输出流
PrintStream ps=new PrintStream(new FileOutputStream("D:/data/txt/a1.txt"));
//1将标准输出流重定向到文件
System.setOut(ps);
//向文件中输出内容
System. out. println("我的测试,重定向到文件hello !");
System.out.println(new ReOut());
任务使用对象流读写对象信息
关键步骤如下。
➢使用序列化将对象保存到文件中。
➢使用反序列化从文件中读取对象信息。
认识序列化
在开发中,经常需要将对象的信息保存到磁盘中便于以后检索。可以使用IOl流中学习的方法逐对对 象的属性信息进行操作,但是这样做通常很烦琐,而且容易出错。可以想象一下编写包含大量对象的大型业务应用程序的情形,程序员不得不为每一个对象编写代码,以便将字段和属性保存至磁盘以及从磁盘还原这些字段和属性。序列化提供了轻松实现这个目标的快捷方法。简单地说,序列化就是将对象的状态存储到特定存储介质中的过程,也就是将对象状态转换为可保持或可传输格式的过程。在序列化过程中,会将对象的公有成员、私有成员包括类名,转换为字节流,然后再把字节流写入数据流,存储到存储介质中,这里说的存储介质通常指的是文件。
使用序列化的意义在于将Java对象序列化后,可以将其转换为字节序列,这些字节序列可以被保存在磁盘上,也可以借助网络进行传输,同时序列化后的对象保存的是二进制状态,这样实现了平台无关性。即可以将在Windows操作系统中实现序列化的一个对象, 传输到UNIX操作系统的机器上,再通过反序列化后得到相同对象,而无需担心数据因平台问题显示异常。
序列化保存对象信息
序列化机制允许将实现了序列化的 Java对象转换为字节序列,这个过程需要借助于I/O流来实现。
java中只有实现了java.io…Serializable接口的类的对象才能被序列化,Serializable表示可串行的,可序列化的,所以,对象序列化在某些文献上也被称为串行化。JDK类库中有些类,如String类,包装类和Date类等都实现了Serializable接口。
对象序列化的步骤很简单,可以概括成如下两大步:
(1)创建一个对象输出流(ObjectOutputStream),它可以包装一个其他类型的输出流,如文件输出流FileOutputStream.
例如以下代码:
ObjectOutputStream oos= new ObjectOutputStream(new FileOutputStream("D:/data/txt/a.txt"));
创建了对象输出流oos,包装了一个文件输出流,即D盘文文件夹txt中的a.txt 文件流。
(2)通过对象输出流的writeObjet()方法写对象,也就是输出可序列化对象。
示例7
使用序列化将学生对象保存到文件中。
实现步骤如下。
1)引入相关类。
2)创建学生类,实现Seializable接口。
3)创建对象输出流。
4)调用writeObject()方法将对象写入文件。
5)关闭对象输出流。
关键代码:
public class Student implements java.io.Serializable {
//Student类的字段和方法省略
public class SerializableObj {
public static void main(String[] args) {
ObjectOutputStream oos = null;
}
//创建ObjectOutputStream输出流
oos=new
ObjectOutputStream(new FileOutputStream("C:\\myDoc\\stu.txt"));
Student stu = new Student("安娜", 30, "女");
//对象序列化,写入输出流
oos.writeObject(stu);
}catch(
IOException ex){
ex.printStackTrace();
}finally{
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStrackTrace();
}
}
}
}
示例7执行完毕后,stu这个学生对象保存到stu.txt文件中。
在示例7中,将一个学生对象保存到文件中,当需要保存多个学生对象时该如何呢?
可以使用集合保存多个学生对象,然后将集合中所有的对象写入文件中。示例7中的、的关键代码可以修改为如下代码,则实现了将两个学生对象写导入到文件中。
// 之前部分代码省略
//创建ObjectOutputStream1输出流
oos=new ObjectOuputStream(new FileOutputStream("D:/data/txt/a.txt"));
Sudent stu =new Student("安娜",30,"女");
Sudent stu =new Student(" 李惠",20,"女");
ArayList<Student> list= new AryList<Student>();
list.add(stu);
list.add(stul);
//对象序列化,写入输出流
oos. writeObject(list);
//之后部分代码省略
反序列化获取对象信息
既然能将对象的状态保存到存储介质(如文件)中,那么如何将这些对象状态读与出来呢?这就用到反序列化。反序列化,顾名思义就是与序列化相反,序列化是将对象的状态信息保存到存储介质中,反序列化则是从特定存储介质中读取数据并重新构建成对象的过程。通过反序列化,可以将存储在文件上的对象信息读取出来,然后重新构建为对象。这样就不需要再将文件上的信息一读取、分析再组织为对象。
反序列化的步骤大致概括为以下两步。
(1)创建一个对象 输入流(ObjectInputStream), 它可以包装一个其他类 型的输入流,如文件输入流FileInputStream。
(2)通过对象输入流的readObject()方法读取对象,该方法返回一个Object类型的对象,如果程序知道该Java对象的类型,则可以将该对象强制转换成其真实的类型。下面接着对刚才的Student类的对象序列化信息,进行反序列化处理。
示例8
使用反序列化读取文件中的学生对象。
实现步骤如下。
1)引入相关类。
2)创建对象输入流。
3)调用readObject 0方法读取对象。
4)关闭对象输入流。
关键代码:
ObjectInputStream ois null;
try
{
//创建OectnpuStrcaen输入流
ois = new ObjectInputStream(new FileInputStream("D:/data/txt/a.txt");
//反序列化,进行强转类型转换
Student stu = (Student) ois.readObject());
//输出生成后的对象信息
System.out.println(" 姓名为: " + stu.getName()):
System.out.println("年龄为: " + stu.getAge());
System.out.println(" 性别为: " + stu.getGender());:
}catch(
IOException ex)
{
ex.printStackTrace();
}finally
{
if (ois != null) {
try {
ois.close();
} catch (OException e) {
e.printStackTrace();
}
}
}
同样,前面以集合的方式将两个学生对象写入到文件,此时该如何反序列化呢?很简单,代码如下:
//创建ObjectInputStream输入流
ois=new ObjectInputStream(new FileInputStream("D:/data/txt/a.txt"));
//反序列化,进行强转类型转换
ArrayList<Student> list = (ArrayList<Student>) ois.readObject();
//Student stu=(Student)ois.readObject(;
//1输出生成后的对象信息
for(
Student stu:list)
{
System.out.println(" 姓名为: " + stu.getName());
System.out.println(" 年龄为: " + stu.getAge());
System.out.printIn(" 性别为: " + stu.getGender());
}
注意
①如果向文件中使用序列化机制写入多个对象,那么反序列化恢复对象时,必须按照写入的顺序读取。
②如果一个可序列化的类,有多个父类(包括直接或间接父类),则这些父类要么是可序列化的,要么有无参数的构造器;否则会抛出异常。
23:44:31
通常,对象中的所有属性都会被序列化,但是对于一些比较敏感的信息,如用户的密码,一旦序列化后, 人们完全可以通过读取文科或拦截网络传输数据的方式获得这些信息。因此,出于对安全的考虑,解决的办法是使用transient来修饰。
对象引用的序列化
如果一个类的成员包含其他类的对象,如班级类中包含学生类型的对象,那么当要序列化班级对象时,则必须保证班级类和学生类都是可序列化的。即当需要序列化某个
特定对象时,它的各个成员对象也必须是可序列化的。要理解这一点,需要了解序列化算法的相关知识,序列化的算法规则如下。
➢所有保存到磁盘中的对象都有一个序列号。
➢当程序试图序列化一个对象时,将会检查是否已经被序列化,只有序列化后的自描述对象才能被转换成字节序列输出。
➢如果对象已经被序列化,则程序直接输出一个序列化编号,而不再重新序列化。至此,任务2已经全部完成,通过学习本部分的内容,可以掌握序列化和反序列化的相关技术。