Java序列化与数据传输

1)什么是序列化

① 序列化:Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。

堆内存中的java对象数据,通过某种方式把该对象存储到磁盘文件中,或者传递给其他网络节点(网络传输)。

对象序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可传递性。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。核心作用是对象状态的保存与重建。

② 反序列化:将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,也就是说,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象。

把磁盘文件中的对象数据或者把网络节点上的对象数据,恢复成Java对象模型的过程。

客户端从文件中或网络上获得序列化后的对象字节流,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象。

整个过程都是 Java 虚拟机(JVM)独立的,也就是说,在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象。

③ 什么场景下会使用到序列化:

暂存大对象;

Java对象需要持久化的时候;

需要在网络,例如socket中传输Java对象。因为数据只能够以二进制的形式在网络中进行传输,因此当把对象通过网络发送出去之前需要先序列化成二进制数据,在接收端读到二进制数据之后反序列化成Java对象;

跨虚拟机通信;

2)实现Java序列化

ObjectInputStreamObjectOutputStream 是高层次的数据流,包含了序列化和反序列化的方法。

ObjectOutputStream 类包含很多写方法来写各种数据类型,但是一个特别的方法例外:

public final void writeObject(Object x) throws IOException

上面的方法序列化一个对象,并将它发送到输出流。相似的 ObjectInputStream 类包含如下反序列化一个对象的方法:

public final Object readObject() throws IOException, ClassNotFoundException                 

该方法从流中取出下一个对象,并将对象反序列化。它的返回值为Object,因此,你需要将它转换成合适的数据类型。

ObjectOutputStream - writeObject - 序列化一个对象,并将它发送到输出流。

ObjectInputStream  - readObject - 从输入流中取出下一个对象,并将对象反序列化。

① 实现Serializable接口;

public class Employee implements java.io.Serializable
{
   private static final long serialVersionUID = 1L;
   private String name;
   private String address;
   public transient int SSN;
   private int number;
   public void mailCheck()
   {
      System.out.println("Mailing a check to " + name + " " + address);
   }
}

一个类的对象要想序列化成功,必须满足两个条件:

该类必须实现 java.io.Serializable 接口。

该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的。

② 进行序列化;

public class SerializeDemo
{
   public static void main(String [] args)
   {
      Employee e = new Employee();
      e.name = "Reyan Ali";
      e.address = "Phokka Kuan, Ambehta Peer";
      e.SSN = 11122333;
      e.number = 101;
      try
      {
         FileOutputStream fileOut =
         new FileOutputStream("/tmp/employee.ser");
         ObjectOutputStream out = new ObjectOutputStream(fileOut);
         out.writeObject(e);
         out.close();
         fileOut.close();
         System.out.printf("Serialized data is saved in /tmp/employee.ser");
      }catch(IOException i)
      {
          i.printStackTrace();
      }
   }
}

③ 进行反序列化;

public class DeserializeDemo
{
   public static void main(String [] args)
   {
      Employee e = null;
      try
      {
         FileInputStream fileIn = new FileInputStream("/tmp/employee.ser");
         ObjectInputStream in = new ObjectInputStream(fileIn);
         e = (Employee) in.readObject();
         in.close();
         fileIn.close();
      }catch(IOException i)
      {
         i.printStackTrace();
         return;
      }catch(ClassNotFoundException c)
      {
         System.out.println("Employee class not found");
         c.printStackTrace();
         return;
      }
    }
}

readObject() 方法中的 try/catch代码块尝试捕获 ClassNotFoundException 异常。对于 JVM 可以反序列化对象,它必须是能够找到字节码的类。如果JVM在反序列化对象的过程中找不到该类,则抛出一个 ClassNotFoundException 异常。

序列化流程:

 1)定义一个类,实现Serializable接口;

 2)在程序代码中创建对象后,创建对象输出流ObjectOutputStream对象并在构造参数中指定流的输出目标(比如一个文件),通过objectOutputStream.writeObject(obj)把对象序列化并输出到流目标处;

 3)在需要提取对象处:创建对象输入流ObjectInputStream对象并在构造参数中指定流的来源,然后通过readObject()方法获取对象,并通过强制类型转换赋值给类对象引用。

3)序列化过程中几个需要注意的点

① 序列化ID,我们在进行序列化时,加了一个serialVersionUID字段,这便是序列化ID;

 private static final long serialVersionUID = 1L;

虚拟机是否允许对象反序列化,不是取决于该对象所属类路径和功能代码是否与虚拟机加载的类一致,而是主要取决于对象所属类与虚拟机加载的该类的序列化 ID 是否一致

Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地实体类中的serialVersionUID进行比较,如果相同则认为是一致的,便可以进行反序列化,否则就会报序列化版本不一致的异常。

② 静态变量和transient关键字修饰的变量不能被序列化;

序列化时并不保存静态变量,这其实比较容易理解,序列化保存的是对象的状态,静态变量属于类的状态,因此 序列化并不保存静态变量。transient作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient变量的值设为初始值,如int型的是0。比如上面例子中SSN属性值反序列化后为0.

③ 当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;

4)序列化原理

① 序列化算法会按步骤执行以下事情:

  1)当前类描述的元数据输出为字节序列;【类定义描述、类中属性定义描述】

  2)超类描述输出为字节序列;【如果超类还有超类,则依次递归,直至没有超类】

  3)从最顶层超类往下,依次输出各类属性值描述,直至当前类对象属性值。

  即:从下到上描述类定义,从上往下输出属性值。

② 为什么Java序列化要实现Serializable接口:

package java.io;
public interface Serializable {
}

这是一个空接口,为什么要设计为空?总的就是说安全性问题,假如没有一个接口(即没有Serializable来标记是否可以序列化),让所有对象都可以序列化。那么所有对象通过序列化存储到硬盘上后,都可以在序列化得到的文件中看到属性对应的值(后面将会通过代码展示)。所以最后为了安全性(即不让一些对象中私有属性的值被外露),不能让所有对象都可以序列化。要让用户自己来选择是否可以序列化,因此需要一个接口来标记该类是否可序列化。

5)Serializable和Parcelable的区别和使用

Java 提供了 Serializable 接口,而 Android 提供特有的 Parcelable 接口。

Seralizable相对Parcelable而言,好处就是非常简单,只需对需要序列化的类class执行就可以,不需要手动去处理序列化和反序列化的过程,但是Serializable 在序列化和反序列化过程中大量使用了反射,效率较低,也因此其过程会产生的大量的内存碎片

Parcelable是Android特有的序列化API,它的出现是为了解决Serializable在序列化的过程中消耗资源严重的问题,但是因为本身使用需要手动处理序列化和反序列化过程,会与具体的代码绑定,使用较为繁琐,一般只获取内存数据的时候使用。

二者效率及选择

Parcelable的性能比Serializable好,在内存开销方面较小,所以在内存间数据传输时推荐使用Parcelable,如activity间传输数据,而Serializable可将数据持久化方便保存,所以在需要保存或网络传输数据时选择Serializable,因为Android不同版本Parcelable可能不同,所以不推荐使用Parcelable进行数据持久化。

6)常见的序列化技术

① Java 序列化

优点:java语言本省提供,使用比较方面和简单; 

缺点:不支持跨语言处理、性能相对不是很好,序列化以后产生的数据相对较大;

② XML序列化

XML序列化的好处在于可读性好,方便阅读和调试。但是序列化以后的字节码文件比较大,而且效率不高,适应于对性能不高,而且QPS较低的企业级内部系统之间的数据交换的场景,同时XML又具有语言无关性,所以还可以用于异构系统之间的数据交换和协议。

解析方式:Pull解析和SAX解析;笔者之前的文章《SVG解析》这里面就使用了Pull解析的方式,去解析一份SVG(XML)文件,效率可以保证。

③ JSON序列化

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,相对于XML来说,JSON的字节流较小,在网络传输上更省流量,而且可读性也尚可。现在JSON数据格式的运用是最普遍的。

解析方式:官方提供了JSONObject,也可以使用谷歌的GSON,阿里的FastJSON,美团的MSON等更加优秀的转码工具。

 

参考文章:

《序列化原理(一):从源码理解Serializable》

《java序列化详解》

《Java序列化,看这篇就够了》

《Parcelable 和 Serializable的区别和使用》

 

借着博文记录近期一个Cmake的编译问题::一个项目有段时间没有运行,后续忽然出现下面的问题,

java.io.FileNotFoundException: D:\Work_project\Series_X\Artist Cam Editor\libraries\PixaToon-master\app\.externalNativeBuild\cmake\release\arm64-v8a\android_gradle_build.json

这个文件用来被Android Gradle Plugin中的externalNativeBuild任务解析,将构建命令解析出来,然后编译C/C++代码,最后产生目标so文件。

解决方法:"Build->Refresh linked C++ projects

Java序列化与数据传输_第1张图片

 

 

 

 

 

你可能感兴趣的:(Java,for,Android,基础知识,序列化,反序列化)