声明:本系列只供本人自学使用,勿喷。
对象流其实并不陌生,在生产中经常会使用到,比如以下2个场景。
场景1:两个进程需要在网络上进行对象的传输,比如在Web服务器中将对象信息保存到redis,怎么办?数据以什么形式传输与保存?
答:发送方将对象序列化为binary,接收方将binary反序列化为对象。
场景2:假如Web服务器中有1万用户并发访问,就有可能出现1万个Session对象,内存可能吃不消,怎么办?
答:可以先将一些session序列化到硬盘中,需要时再把保存在硬盘中的对象还原到内存中。
这时就需要对象流,填充到如图所示的IO体系。
一、对象输入流
public class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants
public interface ObjectInput extends DataInput, AutoCloseable
我们将涉及到的父类、接口都讲解一下。
1.DateInput(接口)
该接口提供的方法用于从binary中连续读取n个字节,返回对应的java基本数据类型值。
- 核心方法
// 读取输入流,保存到byte[]
void readFully(byte b[]) throws IOException
void readFully(byte b[], int off, int len) throws IOException
// 读取数据类型对应的字节数,如readBoolean读取1个字节,readInt读取4个字节,readLong读取8个字节
boolean readBoolean() throws IOException;
byte readByte() throws IOException;
int readUnsignedByte() throws IOException;
short readShort() throws IOException;
int readUnsignedShort() throws IOException;
char readChar() throws IOException;
int readInt() throws IOException;
long readLong() throws IOException;
float readFloat() throws IOException;
double readDouble() throws IOException;
//读取连续字节,直到遇到行结束符,将每个byte转换为char,因此不支持UTF
String readLine() throws IOException;
// 读取 UTF编码的binary
String readUTF() throws IOException;
2.ObjectInput(接口)
在DateInput的基础上扩展了对objects、arrays、Strings的支持
- 核心方法
public Object readObject() throws ClassNotFoundException, IOException;
public int read() throws IOException;
public int read(byte b[]) throws IOException;
public int read(byte b[], int off, int len) throws IOException;
3.ObjectInputStream
作用:将binary反序列化为对象
注意:
1.对象的类需要implements Serializable
(子类继承实现Serializable的父类,则可以在自身不实现Serializable的情况下序列化)
2.transient或者static修饰的属性在序列化/反序列过程中会被忽略
3.反序列化创建的是新对象,不会覆盖原有的对象。
4.Externalizable接口继承自Serializable,可以选择序列化部分属性
5.反序列化枚举类不同于普通类,得到的仍然是同一个对象。
public class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants{
public ObjectInputStream(InputStream in) throws IOException {
verifySubclass();
bin = new BlockDataInputStream(in);
handles = new HandleTable(10);
vlist = new ValidationList();
serialFilter = ObjectInputFilter.Config.getSerialFilter();
enableOverride = false;
readStreamHeader();
bin.setBlockDataMode(true);
}
以下类为ObjectInputStream的内部类
3.1 PeekInputStream
提供单字节的peek()功能
private static class PeekInputStream extends InputStream{
private final InputStream in;
PeekInputStream(InputStream in) {
this.in = in;
}
}
- 核心方法
// 返回输入流的下一个字节,特点:连续调用peek()返回的都是同一个字节
int peek() throws IOException
// 返回输入流的下一个字节,特点:每调用一次read(),光标往后移动1个字节
public int read() throws IOException
public int read(byte[] b, int off, int len) throws IOException
// 循环read直到填满 byte[]
void readFully(byte[] b, int off, int len) throws IOException
3.2 BlockDataInputStream
blkmode字段可以让InputStream拥有两种模式:在默认模式下,输入的数据与DataOutputStream格式相同;在“Block Data”模式下,输入是由Block Data标记括起来的数据。当处于默认模式时,不预先缓冲数据; 当处于Block Data模式时,立即读取当前数据块的所有数据。
private class BlockDataInputStream extends InputStream implements DataInput{
private final PeekInputStream in;
private final DataInputStream din;
BlockDataInputStream(InputStream in) {
this.in = new PeekInputStream(in);
din = new DataInputStream(this);
}
/** block data mode */
private boolean blkmode = false;
/******************只有当 blkmode为 true时,以下字段才有效 ********************
/** current offset into buf */
private int pos = 0;
/** end offset of valid data in buf, or -1 if no more block data */
private int end = -1;
/** number of bytes in current block yet to be read from stream */
private int unread = 0;
}
可以注意到这里还构造了DataInputStream对象,DataInputStream是DataInput的实现类,用来从binary中读取基本数据类型的值。
- 核心方法
// 是否开启Block Data,true==on,false==off
boolean setBlockDataMode(boolean newmode) throws IOException
// 跳到当前 block data的end位置之后
void skipBlockData() throws IOException{
while (end >= 0) {
refill();
}
}
// 读取下一个block data的头,当入参canBlock为false时,可能无法读取到完整的头。假如能读到头的话返回block data的长度
private int readBlockHeader(boolean canBlock) throws IOException{
int avail = canBlock ? Integer.MAX_VALUE : in.available();
// 根据in的下一个字节判断头,合理范围是(112,126)
int tc = in.peek();
switch (tc) {
case TC_BLOCKDATA:读取2个字节的头,返回头的第2个字节
case TC_BLOCKDATALONG:读取5个字节的头,返回头的第2-5个字节表示的int值
case TC_RESET:
}
// 用block data重新填充内部缓冲区
private void refill() throws IOException{
1.假如当前block data还有unread的数据,就将这些数据填充到buf,并更新位置的值
int n =in.read(buf, 0, Math.min(unread, MAX_BLOCK_SIZE));
if (n >= 0) {
end = n;
unread -= n;
}
2.假如当前block data没有unread的数据,就readBlockHeader()读取下一个头,并更新位置的值
int n = readBlockHeader(true);
if (n >= 0) {
end = 0;
unread = n;
}
}
// 返回当前block data未消费的数据,(end - pos) + unread
int currentBlockRemaining()
//返回流的下一个字节
int peek() throws IOException
- 其他方法
1.DateInput接口的方法,读取基本数据类型值
public boolean readBoolean() throws IOException
public char readChar() throws IOException
public int readInt() throws IOException
...
2.读取连续的基本数据类型值到数组中
void readBooleans(boolean[] v, int off, int len) throws IOException
void readChars(char[] v, int off, int len) throws IOException
void readInts(int[] v, int off, int len) throws IOException
...
3.InputStream的方法
public int read(byte[] b, int off, int len) throws IOException
public long skip(long len) throws IOException
public int available() throws IOException
...
回到ObjectInputStream的核心方法
// 实际调用 readObject0
public final Object readObject() throws IOException, ClassNotFoundException
private Object readObject0(boolean unshared) throws IOException {
}
二. 对象输出流
ObjectOutputStream
作用:将对象序列化为binary
由于ObjectOutputStream源码结构与ObjectInputStream相似,不再深究。
public class ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstants
{
public ObjectOutputStream(OutputStream out) throws IOException {
verifySubclass();
bout = new BlockDataOutputStream(out);
handles = new HandleTable(10, (float) 3.00);
subs = new ReplaceTable(10, (float) 3.00);
enableOverride = false;
writeStreamHeader();
bout.setBlockDataMode(true);
if (extendedDebugInfo) {
debugInfoStack = new DebugTraceInfoStack();
} else {
debugInfoStack = null;
}
}
}
- 核心方法
// 实际调用 writeObject0
public final void writeObject(Object obj) throws IOException
private void writeObject0(Object obj, boolean unshared) {
根据obj的类型来判断调用以下哪个write方法
writeNull();
writeHandle(h);
writeClass((Class) obj, unshared);
writeClassDesc((ObjectStreamClass) obj, unshared);
writeString((String) obj, unshared);
writeArray(obj, desc, unshared);
writeEnum((Enum<?>) obj, desc, unshared);
writeOrdinaryObject(obj, desc, unshared);
}
4.demo
class Person implements Serializable {
private static final long serialVersionUID = 42L;
private String name;
private int age;
...
}
// 继承实现 Serializable 的父类,则子类也可以序列化
class Student extends Person{
private Double grade;
...
}
public class Test{
public static void main(String[] args) throws IOException, ClassNotFoundException {
try (
OutputStream outputStream = Files.newOutputStream(Paths.get("D:\\test.txt"));
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
InputStream inputStream=Files.newInputStream(Paths.get("D:\\test.txt"));
ObjectInputStream objectInputStream=new ObjectInputStream(inputStream);
) {
//写入对象
Person person = new Person();
person.setAge(10);
person.setName("张三");
objectOutputStream.writeObject(person);
//写入基本数据类型
objectOutputStream.writeDouble(20.00);
objectOutputStream.writeObject(new Integer(100));
// 读取对象
Person person1 = (Person) objectInputStream.readObject();
// 读取基本数据类型
objectInputStream.readDouble();
Integer i= (Integer) objectInputStream.readObject();
// 写入子类对象
Student student=new Student();
student.setGrade(95.5);
student.setName("李四");
student.setAge(20);
objectOutputStream.writeObject(student);
// 读取子类对象
Student student1= (Student) objectInputStream.readObject();
}
}
三、总结
- 对象类需要实现Serializable
- transient和static属性不参与序列化过程
- objectOutputStream.writeObject()将对象序列化
- objectInputStream.readObject()反序列化创建对象
注:由于该部分源码较多,以后有时间再详细补充,小白我得赶紧去下一个战场了。