一、基础概念
Java中使用IO(输入输出)来读取和写入,读写设备上的数据、硬盘文件、内存、键盘…,根据数据的走向可分为输入流和输出流,这个走向是以内存为基准的,即往内存中读数据是输入流,从内存中往外写是输出流。
根据处理的数据类型可分为字节流和字符流
1.字节流可以处理所有数据类型的数据,在java中以Stream结尾
2.字符流处理文本数据,在java中以Reader和Writer结尾。
字符与字节的关系
不同的字符所占的字节是不同的。
ASCII码:
一个英文字母(不分大小写)占一个字节的空间,一个中文汉字占两个字节的空间。一个二进制数字序列,在计算机中作为一个数字单元,一般为8位二进制数,换算为十进制。最小值0,最大值255。如一个ASCII码就是一个字节。
UTF-8编码:
一个英文字符等于一个字节,一个中文(含繁体)等于三个字节。
Unicode编码:
一个英文等于两个字节,一个中文(含繁体)等于两个字节。
符号:
英文标点占一个字节,中文标点占两个字节。举例:英文句号“.”占1个字节的大小,中文句号“。”占2个字节的大小。
1.1 IO流的结构
二、序列化原理
2.1 基础概念
Java 1.1 增添了一种有趣的特性,名为“对象序列化”( Object Serialization)。它面向那些实现了Serializable 接口的对象,可将它们转换成一系列字节,并可在以后完全恢复回原来的样子。
2.2 序列化过程
序列化原理请参考如下代码
import java.io.*;
/**
* 序列化工具
* Created by daiwei on 2018/1/31.
*/
public abstract class SerializationUtils {
/**
* Serialize the given object to a byte array.
* <P>
* 1.创建一个 ByteArrayOutputStream输出流baos
* 2.创建一个ObjectOutputStream输出流oos,并且以baos作为构造参数
* 3.将指定的对象写入 ObjectOutputStream
* 4.刷新该流的缓冲
* 5.以 byte 数组的形式返回此输出流的当前内容
* </P>
* @param object the object to serialize
* @return an array of bytes representing the object in a portable fashion
*/
public static byte[] serialize(Object object) {
if (object == null) {
return null;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
try {
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(object);
oos.flush();
}
catch (IOException ex) {
throw new IllegalArgumentException("Failed to serialize object of type: " + object.getClass(), ex);
}
return baos.toByteArray();
}
/**
* Deserialize the byte array into an object.
* <P>
* 1.创建一个ByteArrayInputStream输入流,并以bytes作为构造参数
* 2.创建一个ObjectInputStream输入流ois,并且以ByteArrayInputStream输入流作为构造参数
* 3.从 ObjectInputStream 读取对象。
* </P>
* @param bytes a serialized object
* @return the result of deserializing the bytes
*/
public static Object deserialize(byte[] bytes) {
if (bytes == null) {
return null;
}
try {
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
return ois.readObject();
}
catch (IOException ex) {
throw new IllegalArgumentException("Failed to deserialize object", ex);
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException("Failed to deserialize object type", ex);
}
}
}
2.3 应用场景
1.对象持久化。
如写入磁盘,存储到redis等nosql数据库
2.远程方法调用( RMI)
使本来存在于其他机器的对象可以表现出好象就在本地机器上的行为。将消息发给远程对象时,需要通过对象序列化来传输参数和返回值。
思考
1.为何Serializable设计成一个空接口
该Serializable接口仅是一个标记,没有方法,当执行序列化方法时,首先判断这个类是否实现了Serializable接口,如果没有实现则抛出异常等,实现了Serializable接口则继续往下执行序列化相关代码。(和使用注解的思想一致)
jdk部分源码如下(在public final void writeObject(Object obj)方法里实现):
// remaining cases
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum) obj, desc, unshared);
} else if (obj instanceof Serializable) { //判断对象是否实现了Serializable接口
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
2.java 对象反序列化报错:invalid stream header: 32303137问题
对于获取字节数组使用标准的序列化方式,尽量不用String 对象的 getBytes() 获取的 byte。
可参考如下:
/**
* 获得byte[]型的key
* @param sessionId
* @return
*/
private byte[] getByteKey(Serializable sessionId) {
try {
//通过序列化生成 sessionId的字节数组
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos =new ObjectOutputStream(bos);
oos.writeObject(sessionId);
return bos.toByteArray();
} catch (Exception e) {
logger.error("通过序列化生成 sessionId的字节数组失败!,转为字符获取字节数组,sessionId:",sessionId);
String preKey = (String) sessionId;
return preKey.getBytes();
}
}
@see java 对象反序列化报错:invalid stream header: 32303137 https://www.jianshu.com/p/a8b41950c824
参考资料
1.Java IO基础总结https://www.cnblogs.com/dreamyu/p/6551137.html