文章目录
一、序列化
1.1 概述
-
通俗定义
序列化(Serialization)是将对象的状态信息转化为可以存储或者传输的形式的过程,一般将一个对象存储到一个储存媒介,例如档案或记忆体缓冲等,在网络传输过程中,可以是字节或者XML等格式;而字节或者XML格式的可以还原成完全相等的对象,这个相反的过程又称为反序列化; -
Java中定义
在Java中,我们可以通过多种方式来创建对象,并且只要对象没有被回收我们都可以复用此对象。但是,我们创建出来的这些对象都存在于JVM中的堆(heap)内存中,只有JVM处于运行状态的时候,这些对象才可能存在。一旦JVM停止,这些对象也就随之消失;
但是在真实的应用场景中,我们需要将这些对象持久化下来,并且在需要的时候将对象重新读取出来,所以Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据、对象的类型和对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据、对象的类型和对象中存储的数据信息,都可以用来在内存中创建对象。看图理解序列化:
1.2 序列化流【ObjectOutputStream】
java.io.ObjectOutputStream extends OutputStream
作用:将Java对象的原始数据类型写出到文件,实现对象的持久存储。
- 构造方法 :
ObjectOutputStream(OutputStream out)
:创建写入指定OutputStream
的ObjectOutputStream
。- 参数:
OutputStream out
:字节输出流
- 参数:
- 特有的成员方法 :
public final void writeObject(Object obj)
:将指定的对象写入ObjectOutputStream
。
1.3 序列化操作
-
一个对象要想序列化,必须满足两个条件:
- 该类必须实现
java.io.Serializable
接口,Serializable
是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
。 - 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用
transient
关键字修饰。
public class Person implements Serializable{ private String name; //private static int age; private String address; private transient int age; // transient瞬态修饰成员,不会被序列化 public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public Person(String name, String address, int age) { this.name = name; this.address = address; this.age = age; } //省略get/set方法 }
注意:static关键字:静态关键字
静态优先于非静态加载到内存中(静态优先于对象进入到内存中)
被static修饰的成员变量也是不能被序列化的,序列化的都是对象 - 该类必须实现
-
写出对象方法
public final void writeObject (Object obj)
: 将指定的对象写出。public class Demo01ObjectOutputStream { public static void main(String[] args) throws IOException { //1.创建ObjectOutputStream对象,构造方法中传递字节输出流 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("10_IO\\person.txt")); //2.使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中 oos.writeObject(new Person("小美女","南京",18));// 姓名,地址被序列化,年龄没有被序列化。 //3.释放资源 oos.close(); } }
1.4 反序列化流 【ObjectInputStream】
java.io.ObjectInputStream extends InputStream
作用:将之前使用 ObjectOutputStream
序列化的原始数据恢复为对象。
-
构造方法
ObjectInputStream(InputStream in)
:创建从指定InputStream
读取的ObjectInputStream
。- 参数:
InputStream in
:字节输入流
- 参数:
-
特有的成员方法
public final Object readObject ()
: 从ObjectInputStream
读取对象。 -
反序化的前提
- 类必须实现
Serializable
- 必须存在类对应的 class 文件
- 类必须实现
1.5 反序列化操作
反序列化操作一
如果能找到一个对象的class文件,我们可以进行反序列化操作,调用 ObjectInputStream
读取对象的方法:
-
public final Object readObject ()
: 读取一个对象。public class Demo02ObjectInputStream { public static void main(String[] args) throws IOException, ClassNotFoundException { //1.创建ObjectInputStream对象,构造方法中传递字节输入流 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("10_IO\\person.txt")); //2.使用ObjectInputStream对象中的方法readObject读取保存对象的文件 Object o = ois.readObject(); //3.释放资源 ois.close(); //4.使用读取出来的对象(打印) System.out.println(o);//Person{name='小美女', address='南京', age=0} 0 是int的默认值 Person p = (Person)o; System.out.println(p.getName()+p.getAddress()+p.getAge());//小美女南京0 } }
对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException
异常。
反序列化操作二
另外,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException
异常。发生这个异常的原因如下:
- 该类的序列版本号与从流中读取的类描述符的版本号不匹配 【每次修改都会生产新的序列号】
- 该类包含未知数据类型
- 该类没有可访问的无参数构造方法
为了解决上述问题:Serializable
接口给需要序列化的类,提供了一个序列版本号。serialVersionUID
该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
public class Person implements Serializable{
//加入系列化版本号
private static final long serialVersionUID = 1L;
private String name;
//private static int age;
private String address;
private transient int age; // transient瞬态修饰成员,不会被序列化
// 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值.
public int pid;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(String name, String address, int age) {
this.name = name;
this.address = address;
this.age = age;
}
//省略get/set方法
}
1.6 练习:序列化集合
- 将存有多个自定义对象的集合序列化操作,保存到
list.txt
文件中。 - 反序列化
list.txt
,并遍历集合,打印对象信息。
案例分析
- 定义一个存储Person对象的
ArrayList
集合 - 往
ArrayList
集合中存储Person对象 - 创建一个序列化流
ObjectOutputStream
对象 - 使用
ObjectOutputStream
对象中的方法writeObject
,对集合进行序列化 - 创建一个反序列化
ObjectInputStream
对象 - 使用
ObjectInputStream
对象中的方法readObject
,读取文件中保存的集合 - 把
Object
类型的集合转换为ArrayList
类型 - 遍历
ArrayList
集合 - 释放资源
案例实现
ublic class Demo03Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//1.定义一个存储Person对象的ArrayList集合
ArrayList<Person> list = new ArrayList<>();
//2.往ArrayList集合中存储Person对象
list.add(new Person("张三",18));
list.add(new Person("李四",19));
list.add(new Person("王五",20));
//3.创建一个序列化流ObjectOutputStream对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("10_IO\\list.txt"));
//4.使用ObjectOutputStream对象中的方法writeObject,对集合进行序列化
oos.writeObject(list);
//5.创建一个反序列化ObjectInputStream对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("10_IO\\list.txt"));
//6.使用ObjectInputStream对象中的方法readObject读取文件中保存的集合
Object o = ois.readObject();
//7.把Object类型的集合转换为ArrayList类型
ArrayList<Person> list2 = (ArrayList<Person>)o;
//8.遍历ArrayList集合
for (Person p : list2) {
System.out.println(p);
}
//9.释放资源
ois.close();
oos.close();
}
}
二、打印流
2.1 概述
平时我们在控制台打印输出,是调用 print
方法和 println
方法完成的,这两个方法都来自于java.io.PrintStream
类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。
2.2 PrintStream类
java.io.PrintStream extends OutputStream
-
PrintStream特点:
- 只负责数据的输出,不负责数据的读取
- 与其他输出流不同,
PrintStream
永远不会抛出IOException
- 有特有的方法,print,println
void print(任意类型的值)
- ``void println(任意类型的值并换行)`
-
继承自父类的成员方法 :
public void close()
:关闭此输出流并释放与此流相关联的任何系统资源。public void flush()
:刷新此输出流并强制任何缓冲的输出字节被写出。public void write(byte[] b)
:将 b.length字节从指定的字节数组写入此输出流。public void write(byte[] b, int off, int len)
:从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。public abstract void write(int b)
:将指定的字节输出流。
注意:
如果使用继承自父类的write
方法写数据,那么查看数据的时候会查询编码表 97->a
如果使用自己特有的方法print/println
方法写数据,写的数据原样输出 97->97 -
构造方法 :
PrintStream(File file)
:输出的目的地是一个文件PrintStream(OutputStream out)
:输出的目的地是一个字节输出流PrintStream(String fileName)
:输出的目的地是一个文件路径
-
代码演示
public class Demo01PrintStream { public static void main(String[] args) throws FileNotFoundException { //System.out.println("HelloWorld"); //创建打印流PrintStream对象,构造方法中绑定要输出的目的地 PrintStream ps = new PrintStream("10_IO\\print.txt"); //如果使用继承自父类的write方法写数据,那么查看数据的时候会查询编码表 97->a ps.write(97); // a //如果使用自己特有的方法print/println方法写数据,写的数据原样输出 97->97 ps.println(97); // 97 ps.println(8.8); // 8.8 ps.println('a'); // a ps.println("HelloWorld"); // HelloWorld ps.println(true); // true //释放资源 ps.close(); } }
2.3 改变打印流向
System.out
就是PrintStream
类型的,只不过它的流向是系统规定的,打印在控制台上。
我们还可以使用 System.setOut
方法改变输出语句的目的地,改为参数中传递的打印流的目的地
static void setOut(PrintStream out)
重新分配“标准”输出流。
public class Demo02PrintStream {
public static void main(String[] args) throws FileNotFoundException {
System.out.println("我是在控制台输出");
PrintStream ps = new PrintStream("10_IO\\目的地是打印流.txt");
System.setOut(ps);//把输出语句的目的地改变为打印流的目的地
System.out.println("我在打印流的目的地中输出");
ps.close();
}
}