序列化概念 (Serialization)
将对象的状态信息通过某种编码转化为二进制,可以存储或传输。
序列化的好处和目的:
- 实现了数据的持久化,通过序列化可以把数据永久的保存再硬盘上,通常是存在文件中;
- 利用序列化实现远程通信,在网络上传送对象的字节序列,将对象从一个地方传递到另一个地方,
eg:spark程序中将一个driver端的实例对象传送到executor中执行,此对象必须可序列化;
– 序列化: 把对象转成字节序列;
– 反序列化: 把字节序列转化为对象;
– 持久化: 把内存数据存储到磁盘上(一般数据库)
jdk序列化方式(serializable)
序列化操作
- 类实现Serializable接口
public class Book implements Serializable{
//序列化类:java.ioObjectOutputStream讲对象变为指定的二进制数据
//序列化ID,不一致可能会导致序列化失败
private static final long serialVersionUID = 1L;
private String title;
private double price;
public Book(String tit,double pri){
this.title=tit;
this.price=pri;
}
- 调用 ObjectOutputStream 的 writeObject 方法
在这里插入代码片
反序列化操作
- 调用 ObjectInputStream 的 readObject 方法
在这里插入代码片
无序列化
实现了序列化接口的类的实例对象序列化操作时是将整个对象的所有属性序列化;
但是类某些属性不需要序列化;
通过transient关键字来定义:private transient String title; 可以使title属性无法被序列化。
特点:
- jdk的序列化过程会将 ‘类名’ ‘字段名’ ‘父类和子类关系’ 等 元信息 一同序列化,序列化后的文件会变大很多,显得臃肿。
- 反序列化过程中,读取的顺序必须和序列化时的顺序保持一致,有序解码;
- serializable只是一个可序列化的标记;可以规避一些不适合序列化的对象,如mysql的connection连接对象;
Hadoop的MapReduce序列化方式(Writable)
应用场景:map端需要将javaBean以key或value传输给reduce,跨机器通过网络通信传输,这个Javabean就必须实现Writable接口,重写序列化和反序列方法。
eg:
public class Boy implements Writable {
private String name;
private int age;
private String gender;
//序列化,可以按照意愿选择只序列化某些字段
@Override
public void write(DataOutput Out) throws IOException {
Out.writeUTF(this.name);
Out.writeInt(this.age);
Out.writeUTF(this.gender);
}
//反序列化
//反序列化读取顺序要和序列化输出方式保持一致;
@Override
public void readFields(DataInput in) throws IOException {
this.name = in.readUTF();
this.age = in.readInt();
this.gender = in.readUTF();
}
}
特点
- Hadoop的Writable接口,相当于只是提供一种模板,需要程序员手动写入序列化和反序列化的方式和字段;
- 序列化内容不包含类信息和字段信息等元信息,序列化后的文件和实际大小相差无几;
- 和jdk的serializable相比,更灵活,序列化后的文件更小。
Spark的序列化外置框架(kryo)
spark内部默认采用的序列化方式为jdk的serializable,但是对于某些没有实现serializable接口,还不能手动实现的对象,(比如 org.apache.spark.sql.SparkSession 的BloomFilter实例);
若想将BloomFilter以广播变量的形式传送到executor,则可以设置spark序列方式为kryo;
object KryoTest {
def main(args: Array[String]): Unit = {
//设置spark内部使用的序列器为:kryo序列化器
//如果不设置,spark默认使用jdk的序列化器(ObjectOutputStream)
val conf = new SparkConf()
conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
//如果显示注册了需要用kryo序列化的类型,那么kryo在序列化对象时,不需要为每个对象都带上类的元信息,效率更高
conf.registerKryoClasses(Array(classOf[BloomFilter]))
val spark: SparkSession = SparkSession.builder().config(conf)
.appName("sparkKryo序列化")
.master("local[*]")
.getOrCreate()
//参数一(vector):字节向量的长度; 参数二(nbHash):哈希算法的长度; 参数三(hashType):哈希算法的类型
val bloomFilter = new BloomFilter(200000000, 5, 1)
//模拟历史数据
val arr = Array[String]("小白", "小黑", "小黄", "小蓝")
for (elem <- arr) {
//映射数据进布隆过滤器
bloomFilter.add(new Key(elem.getBytes()))
}
//广播
val bmFilter: Broadcast[BloomFilter] = spark.sparkContext.broadcast(bloomFilter)
import spark.implicits._
//模拟查询数据
val names: Dataset[String] = spark.createDataset(List("小白", "小绿"))
//将bloomFilter从driver端传输到executor
val result: Dataset[Boolean] = names.map(x => {
val filter: BloomFilter = bmFilter.value
val isExist: Boolean = filter.membershipTest(new Key(x.getBytes()))
isExist /*+-----+
}) |value|
result.show(100,false) +-----+
spark.close() |true |
} |false|
} +-----+*/
特点
- kryo是一个外部框架,在spark对其进行了整合;
- kryo不需要对要序列化的类进行标记;
- 如果显示注册了需要用kryo序列化的类型,kryo在序列化对象不含类的元信息,效率更高,序列化文件更小;
- 序列化 转化字节数:string= 3+2 ,但会多出2 个字节; int类型 = 4
- 若要单独使用kryo对实力对象进行序列化,则需要导入依赖
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo-shaded</artifactId>
<version>4.0.2</version>
</dependency>
- 使用示范:
public class KryoSerializerDemo {
public static void main(String[] args) {
Boy boy = new Boy("小白", 18, "男");
Kryo kryo = new Kryo();
kryo.register(Boy.class); //注册待序列化的类的class对象
//bufferSize
Output output = new Output(1024);
//序列化
//kryo.writeClassAndObject(output, boy); //带元数据信息,序列化后文件大 44
kryo.writeObject(output, boy); //不带元数据信息,文件小 15 = 3*3 + 4 + 2
byte[] bytes = output.toBytes();
System.out.println(bytes.length);
Input input = new Input(bytes);
//反序列化
//Boy b = (Boy) kryo.readClassAndObject(input);
Boy b = kryo.readObject(input,Boy.class);
System.out.println(b.getAge());
System.out.println(b.getName());
System.out.println(b.getGender());
}
}