小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
详细介绍了Java序列化流ObjectOutputStream、ByteArrayInputStream,内存操作流ByteArrayOutputStream、ByteArrayInputStream,以及实现深克隆。
1 序列化流与反序列化流
1.1 序列化的概念
序列化:指把内存中的Java对象数据,通过某种方式把对象存储到磁盘文件中或者传递给其它网络节点(在网络上传输),这个过程称为序列化。通俗来说就是将数据结构或对象转换成二进制串的过程。
反序列化:把磁盘文件中的对象数据或者把网络节点上的对象数据,恢复成Java对象模型的过程。也就是将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程。
Java怎么进行序列化?
- 需要做序列化的对象的类,必须实现序列化接口:Java.lang.Serializable接口(这是一个标志接口,没有任何抽象方法),Java中大多数类都实现了该接口,比如:String,Integer
- 底层会判断,如果当前对象是Serializable的实例,才允许做序列化,Java对象用instanceof Serializable来判断。
- 在Java中通过序列化对象流来完成序列化和反序列化:
- ObjectOutputStream:通过writeObject()方法做序列化操作。
- ObjectInputStrean:通过readObject()方法做反序列化操作。
涉及的问题:
- 如果某些字段数据不需要做反序列化,则在字段前面加上transient。
- 序列化版本问题,在完成系列化操作后,由于项目的升级或修改,可能我们会对序列化对象进行修改,比如增加某个字段,那么我们再进行反序列化就会报错。
- 解决方法:在JavaBean对象增加一个serialVersionUID字段,用来固定这个版本,无论我们怎么修改,版本都是一致的,就能进行反序列化了。
private static final long serialVersionUID = xxxxxxxxL;
1.2 ObjectOutputStream序列化流
public class ObjectOutputStream
extends OutputStream
implements ObjectOutput, ObjectStreamConstants
ObjectOutputStream 将基本数据和对象写入 OutputStream。通过在流中使用文件可以实现对象的持久存储。
将对象的信息以二进制的形式保存在外部的文件,或者是流当中(通过网络传输数据的时候,也要序列化),这个过程称为序列化。
1.2.1 构造器
public ObjectOutputStream(OutputStream out)
创建写入指定 OutputStream 的 ObjectOutputStream。此构造方法将序列化流部分写入底层流。
1.2.2 API方法
public final void writeObject(Object obj)
将指定的对象写入 ObjectOutputStream。对象的类、类的签名,以及类及其所有超类型的非瞬态和非静态字段的值都将被写入。
注意:序列化的对象必须要实现一个序列化接口Serializable,否则抛出运行时异常NotSerializableException:某个要序列化的对象没有实现 java.io.Serializable 接口。
1.3 ObjectInputStream反序列化流
public class ObjectInputStream
extends InputStream
implements ObjectInput, ObjectStreamConstants
ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。
将外部文件的二进制信息读取到内存当中,这个过程称为反序列化。
实际上是如果能找到一个对象的class文件,我们可以进行反序列化操作,调用ObjectInputStream 读取对象序列化的数据。
对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个ClassNotFoundException 异常。
1.3.1 构造器
public ObjectInputStream(InputStream in)
创建从指定 InputStream 读取的 ObjectInputStream。
1.3.2 API方法
public final Object readObject()
从 ObjectInputStream 读取对象。对象的类、类的签名和类及所有其超类型的非瞬态和非静态字段的值都将被读取。返回Object 对象,使用时需要强转。
注意:此方法将会重构一个新的对象,即相当于对象的深克隆,但是不会触发构造函数的调用。使用后记得关闭两个流。
2 内存操作流
内存操作流一般用于处理临时信息,因为临时信息不需要保存,使用后就可以删除。也可以实现深克隆!
2.1 ByteArrayOutputStream 字节数组输出流
public class ByteArrayOutputStream
extends OutputStream
特点:
- 内部封装了一个字节数组,数组的大小会自动的扩充。
- toString()或者是 toByteArray()方法获得数组内部的数据。
- 该流没有和底层的文件相关联,所以在使用的时候,不需要进行关闭。即使关闭之后,还可以继续使用。
- 该流不会抛出IOException,因为不会涉及到和文件以及网络的交互。
2.1.1 构造器
public ByteArrayOutputStream()
创建一个新的byte数组输出流。缓冲区的容量最初是 32 字节。如有必要可增加其大小。
没有指定任何的目的,其目的就是内部的字节数组。
public ByteArrayOutputStream(int size)
创建一个新的 byte 数组输出流,它具有指定大小的缓冲区容量(以字节为单位)。
2.1.2 API方法
write(int num);
写入一个int 类型的值
write(byte b[] );
将字节数组b 写入到该对象内部封装的数组当中:
toString();
将内部数组当中的数据获得:String返回值
toByteArray();
将内部数组当中的数据写出到一个外部的字节数组当中。
writeTo(OutputStream out);
将内部的数据输出到一个外部的文件当中。
close();
关闭无效。没有和底层的文件进行相关联。
2.2 ByteArrayInputStream 字节数组输入流
public class ByteArrayInputStream
extends InputStream
特点:
- 内部封装了一个字节数组。
- 构造的时候,不需要指定一个文件或者是输入流而是指定的字节数组。
- 该流没有和底层的文件相关联,所以在使用的时候,不需要进行关闭。关闭之后,还可以继续使用。
- 该流不会抛出IOException,因为不会涉及到和文件以及网络的交互。
2.2.1 构造器
public ByteArrayInputStream(byte[] buf)
创建一个ByteArrayInputStream,使用buf作为其缓冲区数组。该缓冲区数组不是复制得到的。
public ByteArrayInputStream(byte[] buf,int offset,int length)
创建 ByteArrayInputStream,使用 buf 作为其缓冲区数组。pos 的初始值是 offset,count 的初始值是 offset+length 和 buf.length 中的最小值。该缓冲区数组不是复制得到的。将该缓冲区的标记设置为指定的偏移量。
1.1.1.2. API方法
public int read()
从此输入流中读取下一个数据字节。返回一个 0 到 255 范围内的 int 字节值。如果因为到达流末尾而没有可用的字节,则返回值 -1。此 read 方法不会阻塞。
public int read(byte[] b)
将数据读取到字节数组b当中
close();
关闭流,此操作无效。
2.3 其他内存操作流
操作字符数组:CharArrayReader、CharArrayWrite
public char[] toCharArray()
操作字符串:StringReader、StringWriter
public StringBuffer getBuffer()
4 案例
4.1 内存流操作
@Test
public void test1() throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write("你好吗?".getBytes());
baos.write("我很好".getBytes());
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
byte[] bytes = new byte[1024];
int read;
while ((read = bais.read(bytes)) != -1) {
System.out.println(new String(bytes, 0, read));
}
}
4.2 实现深克隆
通过序列化和反序列化流以及内存操作流的搭配可以在内存中实现Java的深克隆。
/**
* @author lx
*/
public class ObjectIO {
public static void main(String[] args) {
Student stu = new Student("陈诺", 11);
Teacher t = new Teacher("卫龙", stu);
Teacher t2 = null;
ByteArrayOutputStream baos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bais = null;
ObjectInputStream ois = null;
try {
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(t);
bais = new ByteArrayInputStream(baos.toByteArray());
ois = new ObjectInputStream(bais);
t2 = (Teacher) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (baos != null) baos.close();
if (oos != null) oos.close();
if (bais != null) bais.close();
if (ois != null) ois.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
baos = null;
oos = null;
bais = null;
ois = null;
}
}
//测试深克隆
System.out.println(t);
t2.getStu().setName("小李");
System.out.println(t2);
}
}
class Teacher implements Serializable {
private static final long serialVersionUID = 4L;
private String name;
private Student stu;
public Teacher(String name, Student stu) {
this.name = name;
this.stu = stu;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", stu=" + stu +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student getStu() {
return stu;
}
public void setStu(Student stu) {
this.stu = stu;
}
}
class Student implements Serializable {
private static final long serialVersionUID = 42L;
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!