Java序列化
序列化指的是将Java对象转换成字节序列,便于网络传输和持久化;反序列化指的是将字节序列转换成Java对象。Java对基本数据类型和String类型实现了序列化,不需要显式的序列化/反序列化,直接使用即可。对于一般的类对象,则需要用户显式序列化/反序列化,并且类的成员变量也得是可序列化/反序列化才行。
在Java中,一个对象想要实现序列化,有两种方式:一、实现Serializable接口。二、实现Externalizable接口。后者是Serializable接口的子类,提供了writeExternal()和readExternal()方法可以更加灵活实现序列化。下面重点讲解的是Serializable接口的方法。
序列化/反序列化的详细讲解:
- 需要序列化的类应该实现一个接口Serializable。
Serializable虽然是一个空接口,但是想要一个类的对象能够序列化就必须实现这个接口。因为根据报错信息NotSerializableException定位到ObjectOutputStream.writeObject()方法:里面有一句obj instanceof Seriablizable。仔细分析这个方法可知道,如果一个对象不是字符串不是数组不是枚举也没有实现Serializable接口,就会抛出异常。实现serializable接口只是声明这个类可以被序列化,而真正的序列化动作不需要它实现。
- serialVersionUID
serialVersionUID是一个特殊的静态变量,保证序列化和反序列化的兼容性。每个可序列化的类都有一个serialVersionUID,可以显式指定,如果不显示定义,系统会根据类的结构自动生成。如果不显式声明,对一个类进行修改,那么系统自动生成的serialVersionUID就会发生变化。反序列化时serialVersionUID和序列化时serialVersionUID不一致,就会抛出异常,导致序列化失败。因此,显式声明serialVersionUID可以确保类在发生变化的时候,依然能够正确的序列化反序列化。
- 需要序列化的类的成员变量也必须是可序列化的。
- Java序列化同一个对象,并不会序列化多次。
所有保存到磁盘的对象都有一个序列化编码号。当一个程序试图序列化一个对象,会先检查此对象是否被序列化过,只有对象从没有被序列化过才会转换为字节数组。如果已经被序列化过,直接输出编号。但是由于Java序列化算法不重复序列化同一个对象,只记录编号。如果序列化一个对象之后,这个对象更改内容,再次序列化,也不会将序列化。
- 子类实现序列化,但是父类没有实现序列化
如果父类存在无参构造函数,可以序列化反序列化成功,但是父类的属性其实并没有参与到序列化反序列化的过程中来。
如果父类不存在无参构造函数,父类的属性没有序列化,只序列化子类的属性。不能反序列化
- 在以下两种情况下不进行序列化
1>static修饰的属性不会被序列化:序列化保存的是对象的状态不是类的状态,所以不序列化。static静态字段也是理所当然。
2>transient修饰的属性也不会被序列化:如果在序列化类的对象的时候,不希望某个字段被序列化,就用transient修饰。
虽然Java原生序列化很简单方便,但是它也有无法无法跨语言、序列化后的字节流太大、序列化时间长等缺点。因此,除了Java原生序列化之外,还有很多高效的序列化框架,kyro等。
序列化的应用案例:
1.从外部实现序列化的方式
import java.io.Serializable;
public class Example_Outer implements Serializable {
private String name;
private String address;
private int age;
//省略getter、setter方法
@Override
public String toString() {
return name+" "+address+" "+age;
}
}
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
String path="D:\\Java_Project\\untitled\\a.txt";
serialize(path);
deserialize(path);
}
public static void serialize(String path) throws IOException {
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream(path));
Example_Outer example_outer=new Example_Outer();
example_outer.setAddress("NY");
example_outer.setName("alice");
example_outer.setAge(10);
out.writeObject(example_outer);
out.close();
System.out.println(example_outer.toString());
}
public static void deserialize(String path) throws IOException, ClassNotFoundException {
ObjectInputStream in=new ObjectInputStream(new FileInputStream(path));
Example_Outer example_outer=(Example_Outer) in.readObject();
in.close();
System.out.println(example_outer.toString());
}
}
2.自定义实现序列化的方式
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class Example_Inner implements Serializable {
private static final Long serialVersionUID=10000L;
private String name;
private String address;
private int age;
//省略getter setter
@Override
public String toString() {
return name+" "+address+" "+age;
}
private void readObject(ObjectInputStream in ) throws IOException, ClassNotFoundException {
this.name = ((StringBuffer)in.readObject()).reverse().toString();
this.age = in.readInt();
this.address =(String) in.readObject();
}
private void writeObject(ObjectOutputStream out) throws IOException {
//可以灵活的翻转name
out.writeObject(new StringBuffer(this.name).reverse());
out.writeInt(this.age);
out.writeObject(this.address);
}
}
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
String path="D:\\Java_Project\\untitled\\a.txt";
serialize(path);
deserialize(path);
}
public static void serialize(String path) throws IOException {
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream(path));
Example_Inner example_inner=new Example_Inner();
example_inner.setAddress("NY");
example_inner.setName("alice");
example_inner.setAge(10);
out.writeObject(example_inner);
out.close();
System.out.println(example_inner.toString());
}
public static void deserialize(String path) throws IOException, ClassNotFoundException {
ObjectInputStream in=new ObjectInputStream(new FileInputStream(path));
Example_Inner example_inner=(Example_Inner) in.readObject();
in.close();
System.out.println(example_inner.toString());
}
}
方式二相对于方式一,重写writeObject和readObject。它们都是private方法,但是它却被外部类(ObjectOutputStream和ObjectInputStream)调用,并且它们既不存在与Java.lang.Object,也没有在Serializeable中声明,那它们是如何被ObjectOutputStream和ObjectInputStream调用的呢?因为利用反射机制。ObjectOutputStream和ObjectInputStream使用了反射来寻找是否声明了这两个方法。因为它们使用getPrivateMethod,所以这些方法不得不被声明为priate以至于供ObjectOutputStream来使用。另外,Write的顺序和read的顺序需要对应。譬如writeObject有多个字段都用writeInt写入流中,那么readint需要按照顺序将其赋值。
方式二相对于方式一的好处:
1.性能优化:可以跳过不需要序列化的字段,减少序列化后的字节流大小,提高性能。
2.安全性:对于敏感数据进行加密或者特别处理,提供安全性。
3.版本兼容:可以灵活控制序列化格式,更好的兼容不同版本的类。
Java排序
在Java编程中,经常需要对对象进行排序。为实现排序,Java提供了两种方式:Comparable和Compator。
1、Comparable
java.lang.Comparable接口,允许用户定义对象之间的排序方法。它的内容和使用案例如下:
public interface Comparable<T>{
int compareTo(T o);
//返回0,表示两个对象等于;返回正整数,表示大于;返回负数,表示小于
}
public class Example implements Comparable<Example> {
private String name;
private String address;
private int age;
//省略getter setter方法
@Override
public String toString() {
return name+" "+address+" "+age;
}
@Override
public int compareTo(Example o)
{
return this.age-o.age;
}
}
2.Comparator
java.util.Comparator也是用于定义对象排序的接口,它允许为一个类定义不同的排序方式或者为没有实现Comparable的类定义排序顺序。它的定义和使用如下:
public interface Comparator<T>
{
int compare(T o1, T o2);
//....还有一些方法
}
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.Comparator;
public class Example implements Comparable<Example> {
private String name;
private String address;
private int age;
//省略getter setter方法
@Override
public String toString() {
return name+" "+address+" "+age;
}
@Override
public int compareTo(Example o) {
return this.age-o.age;
}
}
import java.util.Comparator;
public class MyComparator implements Comparator<Example> {
@Override
public int compare(Example o1, Example o2) {
return o2.getAge()-o1.getAge();
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException
{
Example[] array=new Example[3];
Example ex1=new Example();
ex1.setName("a");
ex1.setAge(10);
Example ex2=new Example();
ex2.setName("b");
ex2.setAge(9);
Example ex3=new Example();
ex3.setName("c");
ex3.setAge(8);
array[0]=ex1;
array[1]=ex2;
array[2]=ex3;
//没有指定比较器,如果array的数据类型实现了comparable则用其规定的顺序。
//如果没有实现,抛出异常。
Arrays.sort(array);
//实现Comparator接口,指定了比较器。
MyComparator myComparator=new MyComparator();
Arrays.sort(array,myComparator);
//采用匿名内部类的方式
Arrays.sort(array, new Comparator<Example>() {
@Override
public int compare(Example o1, Example o2) {
return o1.getAge()-o2.getAge();
}
});
}
3.Comparable和Comparator的比较
- 可以理解Comparable是内部比较器,Comparator是外部比较器。如果类使用 Comparable ,那么就要修改类实现 Comparable 接口并重写 compareTo 方法,所以 Comparable 更像是“对内”进行排序的接口。而 Comparator 的使用则不相同,Comparator 无需修改原有类。也就是在最极端情况下,即使类是第三方提供的,我们依然可以通过创建新的自定义比较器 Comparator,来实现对第三方类的排序功能。也就是说通过 Comparator 接口可以实现和原有类的解耦,在不修改原有类的情况下实现排序功能,所以 Comparator 可以看作是“对外”提供排序的接口。
- 使用场景:当类只需要一种排序方式,并且类的所有实例都按照这种方式排序,那么Comparable。当类需要多种排序方式,或者类没有实现Comparable接口也无法修改,用Comparator。
- 如果一个类实现Comparable接口,并且定义了Comparator的时候,Comparable被认为是自然排序顺序,Comparator是可选的。
- Collections.sort()或者Arrays.sort()排序的时候如果不指定Comparator,那就以实现Comparable接口设定的方式排序。
Hadoop序列化
Hadoop中涉及到数据在网络中传输或者存储在磁盘,所以序列化/反序列化在Hadoop中至关重要。而Java序列化附带了很多额外的信息,不适合在网络中高效传输,因此,Hadoop开发了自己的序列化机制,称为Writable。Hadoop的序列化接口是 org.apache.hadoop.io.Writable。和Java一样,Hadoop的一些数据类型已经实现序列化直接使用即可,但是对于一些类还是需要手动进行。
public interface Writable{
void write(DataOutput out) throws IOException;
void readFields(DataInput in) throws IOException;
}
import org.apache.hadoop.io.Writable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class MyWritable implements Writable {
// 一些数据
private int counter;
private long timestamp;
// 默认的构造器,用来支持序列化/反序列化
MyWritable() {
}
public void write(DataOutput out) throws IOException {
out.writeInt(counter);
out.writeLong(timestamp);
}
public void readFields(DataInput in) throws IOException {
counter = in.readInt();
timestamp = in.readLong();
}
public static MyWritable read(DataInput in) throws IOException {
MyWritable w = new MyWritable();
w.readFields(in);
return w;
}
}
Hadoop序列化的步骤:
1.实现Writable接口
2.反序列化时,需要反射调用无参构造函数,因此必须有。(Serializable不通过无参构造函数,所以不“必须有”)
3.重写write()方法和readFields()方法。注意顺序一致。
4.在自定义数据类型中,建议使用java原生数据类型,最好不要使用hadoop对原生类型封装好的数据类型。(Why?)
Hadoop排序
排序在MapReduce框架中是非常重要的操作,而排序是根据Key进行的。如果Key的数据类型是Hadoop实现的数据类型,是按照字典顺序排序。而如果Key的数据类型是自定义的类,那么类就需要实现比较功能。
MapReduce有一下比较方式:
1. WritableComparable:
可以使用java排序中的comparable方法实现,重写compareTo()方法即可。同时,在MapReduce中自定义的类往往也需要实现序列化反序列化,于是将Writable和Comparable结合起来,形成一个新的接口WritableComparable。使用方法和单独使用的时候一样。
public interface WritableComparable<T> extends org.apache.hadoop.io.Writable,java.lang.Comparable<T>{
public void write(DataOutput out) throws IOException;
public void readFields(DataInput in) throws IOException;
public int compareTo(MyWritableComparable o);
}
public class MyWritableComparable implements
WritableComparable <MyWritableComparable> {
private int counter;
private long timestamp;
public void write(DataOutput out) throws IOException {
out.writeInt(counter);
out.writeLong(timestamp);
}
public void readFields(DataInput in) throws IOException {
counter = in.readInt();
timestamp = in.readLong();
}
public int compareTo(MyWritableComparable o) {
int thisValue = this.value;
int thatValue = o.value;
return (thisValue < thatValue ? -1 : (thisValue==thatValue ? 0 : 1));
}
}
2. RawComparator:
如果使用WritableComparable的实现比较,Hadoop的默认方式是先将序列化后的对象字节流反序列化为对象,然后再进行比较(compareTo方法),比较过程需要一个反序列化的步骤,而这一整个过程会产生额外的开销。
Hadoop中提供了原生的比较接口RawComparator,该接口继承于 Java Comparator 接口。RawComparator接口允许其实现直接比较数据流中的记录,无需先把数据流反序列化为对象,这样避免了新建对象的额外开销,从而加速程序的运行。
RawComparator是一个接口。
public interface RawComparator<T> extends java.util.Comparator<T>{
int compare(byte[] bytes,int i,int i1,byte[] bytes2,int i2,int i3);
//除了基于字节的比较方法,还有对象的比较方法(继承了Comparator)
}
public class MyRawComparator implements RawComparator<Example> {
private Example o1=new Example();
private Example o2=new Example();
private DataInputBuffer buffer=new DataInputBuffer();
@Override
public int compare(byte[] bytes,int i,int i1,byte[] bytes2,int i2,int i3)
{
try{
buffer.reset(bytes,i,i1);
o1.readFields(buffer);
buffer.reset(bytes2,i2,i3);
o2.readFields(buffer);
buffer.reset(null,0,0);
}catch( IOException e){
throw new RuntimeException(e);
}
return compare(o1,o2);
}
@Override
public int compare(Example o1,Example o2)
{
//此处定义两个变量比较的方式
return 0;
}
}
在第一个方法是在程序中先调用的,在此方法中将字节数组封装成要比较的类型,然后调用第二个方法。
在完成自定义比较器之后,在Driver中设置:
job.setSortComparatorClass(MyRawComparator.class);
3. WritableComparator:
在Hadoop中编写Writable的RawComparator一般不直接继承RawComparator类,而是继承RawComparator的子类WritableComparator。需要注意的是,继承WritableComparator的类必须是实现WritableComparable接口的类。
Hadoop自身提供的IntWritable、LongWritabe等类通过将WritableComparator的子类作为内部类的方法实现了优化,使得用Key进行比较时,直接使用序列化的字节数组进行比较大小,而不用进行反序列化。而对于自定义的类,需要用户手动设置。
作为实现Writable接口的类的内部类(以IntWritable为例子)
public class IntWritable implements WritableComparable<IntWritable> {
private int value;
public IntWritable() {}
public IntWritable(int value) { set(value); }
/** Set the value of this IntWritable. */
public void set(int value) { this.value = value; }
/** Return the value of this IntWritable. */
public int get() { return value; }
@Override
public void readFields(DataInput in) throws IOException {
value = in.readInt();
}
@Override
public void write(DataOutput out) throws IOException {
out.writeInt(value);
}
/** Returns true iff <code>o</code> is a IntWritable with the same value. */
@Override
public boolean equals(Object o) {
if (!(o instanceof IntWritable))
return false;
IntWritable other = (IntWritable)o;
return this.value == other.value;
}
@Override
public int hashCode() {
return value;
}
/** Compares two IntWritables. */
@Override
public int compareTo(IntWritable o) {
int thisValue = this.value;
int thatValue = o.value;
return (thisValue<thatValue ? -1 : (thisValue==thatValue ? 0 : 1));
}
@Override
public String toString() {
return Integer.toString(value);
}
/** A Comparator optimized for IntWritable. */
public static class Comparator extends WritableComparator {
public Comparator() {
super(IntWritable.class);
}
@Override
public int compare(byte[] b1, int s1, int l1,
byte[] b2, int s2, int l2) {
int thisValue = readInt(b1, s1);
int thatValue = readInt(b2, s2);
return (thisValue<thatValue ? -1 : (thisValue==thatValue ? 0 : 1));
}
}
static { // register this comparator
WritableComparator.define(IntWritable.class, new Comparator());
}
}
和RawComparator一样作为自定义比较器
public class MyWritableComparator extends WritableComparator {
@Override
public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) {
//return super.compare(b1, s1, l1, b2, s2, l2);
//通过WritableComparator自带的相关readxx方法解析出变量然后进行判断
}
}
4. 总结
WritableComparable就像是Java排序中Comparable,是内部比较器。如果使用compreTo定义的方式排序,就直接用即可不需要额外的设置。如果不使用这个compateTo的方法,就需要自定义一个外部比较器,外部比较器需要设置。
对于外部比较器,常使用的是继承WritableComparator方式自定义,因为 compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2)方法已经实现,而RawComparator接口没有实现。但是如果自定义的类没有实现WritableComparable接口,就必须采用RawComparator的方式。
此外,如果不需要使用自定义比较器而又想要避免反序列带来的开销,可以将WritableComparator子类作为自定义类的静态内部类。
参考资料
https://blog.csdn.net/qq_35462323/article/details/106569701
https://mp.weixin.qq.com/s/KNoQha6JR7kn8smpHlMoLg
https://www.jianshu.com/p/352fa61e0512
https://www.cnblogs.com/9dragon/p/10901448.htmlhttps://blog.csdn.net/CSDN_WYL2016/article/details/109114549
https://blog.csdn.net/qq_43193797/article/details/86093138
https://www.cnblogs.com/sunbr/p/13398499.html
https://blog.csdn.net/CPP_MAYIBO/article/details/88074987
https://blog.csdn.net/maixia24/article/details/16964655
https://blog.csdn.net/fengge18306/article/details/105038728https://blog.csdn.net/Doecy/article/details/812969
https://blog.csdn.net/cnkxy68446/article/details/100300236
https://blog.csdn.net/epitomizelu/article/details/117195218 https://blog.csdn.net/chengyuan2789/article/details/100839245 https://blog.csdn.net/weixin_46845300/article/details/110158613 https://blog.csdn.net/m0_37606374/article/details/115276726 https://www.cnblogs.com/smartloli/p/4443714.html https://blog.51cto.com/lawsonabs/3002924 https://www.cnblogs.com/yxym2016/p/14409083.html

1355

被折叠的 条评论
为什么被折叠?



