Java基础知识强化104:Serializable接口 与 Parcelable接口

时间:2022-09-03 10:36:27

1. 什么是 序列化 和 反序列化 ?

    序列化 :序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。 

  反序列化 :是指把这种二进制流数据还原成对象。

什么时候使用序列化:

  1):Java对象序列化可以实现分布式对象。主要应用例如:RMI要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。
  2):Java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的"深复制",即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。

2. 实现序列化方式:

一是:实现Serializable接口(是JavaSE本身就支持的);

二是:实现Parcelable接口(是Android特有功能,效率比实现Serializable接口高效,可用于Intent数据传递,也可以用于进程间通信(IPC))。

实现Serializable接口非常简单,声明一下就可以了,而实现Parcelable接口稍微复杂一些,但效率更高,推荐用这种方法提高性能。

注:

  Android中Intent传递对象有两种方法:一是Bundle.putSerializable(Key,Object),另一种是Bundle.putParcelable(Key,Object)。当然这些Object是有一定的条件的,前者是实现了Serializable接口,而后者是实现了Parcelable接口。

3. Serializable接口 与 Parcelable接口 的区别

(1)Parcelable使用起来稍复杂点,而后者使用起来非常简单。

(2)Parcelable效率比Serializable高,支持Intent数据传递,也支持进程间通信(IPC)。

(3)Parcelable使用时要用到一个Parcel,可以简单将其看为一个容器,序列化时将数据写入Parcel,反序列化时从中取出。

(4)在使用内存的时候,Parcelable比Serializable性能高,所以推荐使用Parcelable。Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC。Parcelable不能使用在要将数据存储在磁盘上的情况,因为Parcelable在外界有变化的情况下不能很好的保证数据的持续性。尽管Serializable效率低点,但此时还是建议使用Serializable 。

 

4. Parcelable 和 Serializable都能实现序列化并且都可用于Intent间传递数据,那么两者之间如何选取呢?

解析:

  Serializable是Java中序列化接口,其使用起来很方便但是开销很大,序列化和反序列化过程中需要大量的I/0操作。而Parcelable是Android中序列化方式,因此更适合在Android平台上,它缺点是使用起来稍微有点麻烦,但是它的效率很高,这是Android推荐的序列化方式,因此在Android开发中首选序列化Parcelable。Parcelable主要用在内存序列化上,通过Parcelable将对象序列化到存储设备中或者将对象序列化后通过网络传输也都是可以的,但是这个过程会稍微复杂一点,因此在这两种情况下建议大家使用Serializable。

5. Serializable接口:

(1)使用Serializable接口步骤:

  )将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的;

  )然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

(2)使用Serializable的Demo:

  1)新建一个Java工程,如下:

Java基础知识强化104:Serializable接口 与 Parcelable接口

  2)同时 User 和 SerializableTest ,如下:

User:

 package com.himi.serializable;

 import java.io.Serializable;

 public class User implements Serializable{
/**
* serialVersionUID:
* Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,
* JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,
* 如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。
*/
private static final long serialVersionUID = 1L;
private String name;
private int age;
private String email; public User(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
} public String getName() {
return name;
} @Override
public String toString() {
return "User [name=" + name + ", age=" + age + ", email=" + email + "]";
} public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
} }

SerializableTest :

 package com.himi.serializable;

 import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; public class SerializableTest { public static void main(String[] args) {
//序列化过程
User user = new User("hebao",24,"123123123");
try {
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("cache.txt") );
out.writeObject(user);
out.close(); System.out.println("------序列化完毕------"); } catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} //反序列化过程
try {
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("cache.txt"));
User newUser = (User) in.readObject();
in.close(); System.out.println("------反序列化完毕------");
System.out.println("newUser name:"+newUser.getName());
System.out.println("newUser age:"+newUser.getAge());
System.out.println("newUser email:"+newUser.getEmail());
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} } }

运行程序,出现Console,如下:

Java基础知识强化104:Serializable接口 与 Parcelable接口

上面中提到的serialVersionUID后面那一串数字又是什么含义?

  我们要明白系统既然指定了serialVersionUID,那么它必须是有用的。这个serialVersionUID是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的 serialVersionUID 只有和当前类的serialVersionUID相同才能够正常地被反序列化。

  serialVersionUID的详细工作机制:序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中(或者其他中介),当反序列化的时候系统会检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID是一致的,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候就可以成功反序列化;否则就说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量,类型可能发生了改变,这个时候是无法之正常反序列化的,因此会报一下错误:

java.io.InvalidClassException: com.tbynet.admin.AdminBean; local class incompatible: stream classdesc serialVersionUID = 7479061626399129990, local class serialVersionUID = -5738694578196915867

  一般来说,我们应该手动指定serialVersionUID的值,比如1L,也可以让Eclipse根据当前类的结构自动去生成它的hash值,这样序列化和反序列化时两者的serialVersionUID是相同的,因此可以正常进行反序列化。如果不手动指定serialVersionUID,反序列化时当前类有所改变,比如增加或者删除了某些成员变量,那么系统就会重新计算当前类的hash值并把它赋给serialVersionUID,这个时候当前类的serialVersionUID就和序列化的数据中的serialVersionUID不一致,于是反序列化失败,程序就会失败crash。所以,我们可以明显感觉到serialVersionUID的作用,当我们手动指定它以后,就可以在很大程度上避免反序列化过程的失败。比如当前版本升级之后,我们可能删除了某个成员变量也可能增加了一些新的成员变量,这个时候我们的反序列化过程中仍然能够成功,程序仍然能够最大限度恢复数据,相反,如果不指定serialVersionUID的话,程序就会挂掉。当然我们还要考虑另外一种情况,如果类结构发生了非常规改变了,比如修改了类名,修改了成员变量的类型,这个时候尽管serialVersionUID验证通过了,但是反序列化过程还是会失败,因为类结构有了毁灭性的改变,根本无法从来老版本的数据中还原一个新的类结构的对象。

  我们从上面知道,给serialVerisonUID指定为1L或者采用Eclipse根据当前类结构去生成hash值,这两者并没有本质区别,效果完全一样。

  以下两点需要注意:

  ()首先静态成员变量属于类不属于对象,所以不会参与序列化过程。static代表类的状态。

  ()其次用transient关键字标记的成员变量不参与序列化过程。 transient代表对象的临时数据。

 

6. Parcelable接口:

Parcelable也是一个接口,只要实现这个接口,一个类的对象就可以实现序列化并可以通过 Intent 和 Binder传递。

(1)Parcelable接口的定义:

public interface Parcelable
{
//内容描述接口,基本不用管
public int describeContents();
//写入接口函数,打包
public void writeToParcel(Parcel dest, int flags);
//读取接口,目的是要从Parcel中构造一个实现了Parcelable的类的实例处理。因为实现类在这里还是不可知的,所以需要用到模板的方式,继承类名通过模板参数传入
//为了能够实现模板参数的传入,这里定义Creator嵌入接口,内含两个接口函数分别返回单个和多个继承类实例
public interface Creator<T>
{
public T createFromParcel(Parcel source);
public T[] newArray(int size);
}
}

(2)Parcelable的方法说明:

Java基础知识强化104:Serializable接口 与 Parcelable接口

系统已经为我们提供许多实现了Parcelabe接口的类,它们都是可以直接进行序列化的,比如Intent、Bundle、Bitmap等等,同时List和Map也是可以序列化的,前提是它们里面的每个元素都是可以序列化的。

(3)使用Parcelable接口步骤:

  )implements Parcelable

  )重写writeToParcel方法,将你的对象序列化为一个Parcel对象,即:将类的数据写入外部提供的Parcel中,打包需要传递的数据到Parcel容器保存,以便从 Parcel容器获取数据

  )重写describeContents方法,内容接口描述,默认返回0就可以

  )实例化静态内部对象CREATOR实现接口Parcelable.Creator

public static final Parcelable.Creator<T> CREATOR

注:其中public static final一个都不能少,内部对象CREATOR的名称也不能改变,必须全部大写。需重写本接口中的两个方法:createFromParcel(Parcel in) 实现从Parcel容器中读取传递数据值,封装成Parcelable对象返回逻辑层,newArray(int size) 创建一个类型为T,长度为size的数组,仅一句话即可(return new T[size]),供外部类反序列化本类数组使用。

简而言之:通过writeToParcel将你的对象映射成Parcel对象,再通过createFromParcel将Parcel对象映射成你的对象。也可以将Parcel看成是一个流,通过writeToParcel把对象写到流里面,在通过createFromParcel从流里读取对象,只不过这个过程需要你来实现,因此写的顺序和读的顺序必须一致。

(4)实现Parcelable接口实体类内部分析:

User:

 package com.himi.parcelabledemo.bean;

 import android.os.Parcel;
import android.os.Parcelable; public class User implements Parcelable
{
private String name;
private int age;
private String email; public User(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
} public User(Parcel source) {
name = source.readString();
age = source.readInt();
email = source.readString();
} public String getName() {
return name;
} @Override
public String toString() {
return "User [name=" + name + ", age=" + age + ", email=" + email + "]";
} public void setName(String name) {
this.name = name;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public String getEmail() {
return email;
} public void setEmail(String email) {
this.email = email;
} /**
* 内容描述
*/
@Override
public int describeContents() {
// TODO Auto-generated method stub
return 0;
} /**
* 序列化过程:必须按成员变量声明的顺序进行封装
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
// TODO Auto-generated method stub
dest.writeString(name);
dest.writeInt(age);
dest.writeString(email); } /**
* 反序列过程:必须实现Parcelable.Creator接口,并且对象名必须为CREATOR
* 读取Parcel里面数据时必须按照成员变量声明的顺序,Parcel数据来源上面writeToParcel方法,
* 读出来的数据供逻辑层使用.
*/
public static final Parcelable.Creator<User> CREATOR = new Parcelable
.Creator<User>() { @Override
public User[] newArray(int size) {
// TODO Auto-generated method stub
return new User[size];
} @Override
public User createFromParcel(Parcel source) {
// TODO Auto-generated method stub
return new User(source);
}
}; }

这里先说明一下Parcel,Parcel内部包装了可序列化的数据,可以在Binder中*传输。

上面代码可以看出:在让实体类User序列化的过程中,需要实现的功能有序列化、反序列化和内容描述。

  • 序列化功能:由writeToParcel方法,最终是通过Parcel中一系列write方法来完成。
  • 反序列化功能:由CREATOR来完成,其内部标明了如何创建序列化对象和数组,并通过Parcel的一系列read方法来完成反序列化过程。
  • 内容描述功能:由describeContents方法来完成,几乎在所有的情况下这个方法都是应该返回 ,仅当当前对象中存在文件描述符时候,才返回 。

(5)使用Parcelable的Demo:

  1)新建一个Android工程,如下:

Java基础知识强化104:Serializable接口 与 Parcelable接口

  2)来到主布局文件activity_main.xml如下:

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.himi.parcelabledemo.MainActivity" > <Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:text="Button" /> </RelativeLayout>

  3)新建一个包:com.himi.parcelabledemo.bean,这里新建一个bean实体类User,如下:

 package com.himi.parcelabledemo.bean;

 import android.os.Parcel;
import android.os.Parcelable; public class User implements Parcelable
{
private String name;
private int age;
private String email; public User(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
} public User(Parcel source) {
name = source.readString();
age = source.readInt();
email = source.readString();
} public String getName() {
return name;
} @Override
public String toString() {
return "User [name=" + name + ", age=" + age + ", email=" + email + "]";
} public void setName(String name) {
this.name = name;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public String getEmail() {
return email;
} public void setEmail(String email) {
this.email = email;
} @Override
public int describeContents() {
// TODO Auto-generated method stub
return 0;
} /**
* 序列化过程:必须按成员变量声明的顺序进行封装
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
// TODO Auto-generated method stub
dest.writeString(name);
dest.writeInt(age);
dest.writeString(email); } /**
* 反序列过程:必须实现Parcelable.Creator接口,并且对象名必须为CREATOR
* 读取Parcel里面数据时必须按照成员变量声明的顺序,Parcel数据来源上面writeToParcel方法,
* 读出来的数据供逻辑层使用.
*/
public static final Parcelable.Creator<User> CREATOR = new Parcelable
.Creator<User>() { @Override
public User[] newArray(int size) {
// TODO Auto-generated method stub
return new User[size];
} @Override
public User createFromParcel(Parcel source) {
// TODO Auto-generated method stub
return new User(source);
}
}; }

  4)接下来,我们来到MainActivity,如下:

 package com.himi.parcelabledemo;

 import com.himi.parcelabledemo.bean.User;

 import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView; public class MainActivity extends Activity { private Button button;
private TextView textview; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); button = (Button) findViewById(R.id.button);
textview = (TextView) findViewById(R.id.textView); button.setOnClickListener(new OnClickListener() { @Override
public void onClick(View v) { Intent in = new Intent(MainActivity.this, SecondActivity.class);
in.putExtra("user", new User("步惊云",23,"1102133@163.com" ));
startActivity(in);
}
});
} }

上面的MainActivity通过Intent跳转到SecondActivity,其中SecondActivity,如下:

 package com.himi.parcelabledemo;

 import com.himi.parcelabledemo.bean.User;

 import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView; public class SecondActivity extends Activity {
private TextView textview; @Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.second); textview = (TextView) findViewById(R.id.textView); User user = getIntent().getParcelableExtra("user");
textview.setText("user name:"+user.getName()+"\n"
+"user age:"+user.getAge()+"\n"
+"user email:"+user.getEmail()); } }

与SecondActivity相对应的布局文件second.xml,如下:

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"> <TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView" /> </LinearLayout>

  5)不要忘了这里我们新建一个SecondActivity,需要在AndroidManifest.xml文件中注册一下

  6)布署程序到模拟器,如下:

Java基础知识强化104:Serializable接口 与 Parcelable接口

Java基础知识强化104:Serializable接口 与 Parcelable接口

Java基础知识强化104:Serializable接口 与 Parcelable接口的更多相关文章

  1. Java基础知识强化之集合框架笔记76:ConcurrentHashMap之 ConcurrentHashMap简介

    1. ConcurrentHashMap简介: ConcurrentHashMap是一个线程安全的Hash Table,它的主要功能是提供了一组和Hashtable功能相同但是线程安全的方法.Conc ...

  2. Java基础知识强化之网络编程笔记25:Android网络通信之 Future接口介绍(Java程序执行超时)

    1. Future接口简介 在Java中,如果需要设定代码执行的最长时间,即超时,可以用Java线程池ExecutorService类配合Future接口来实现. Future接口是Java标准API ...

  3. Java基础知识强化18:抽象类、接口的区别 和 选择性实现接口方法

    1.抽象类和接口的区别 抽象类里面可以有非抽象的方法(可以没有抽象方法),接口里只能有抽象方法. 抽象类中的抽象方法声明时不能有大括号,而接口中的所有方法都没有大括号.  抽象类(abstract c ...

  4. Java基础知识强化之多线程笔记01:多线程基础知识(详见Android(java)笔记61~76)

    1. 基础知识: Android(java)学习笔记61:多线程程序的引入    ~    Android(java)学习笔记76:多线程-定时器概述和使用 

  5. Java基础知识强化之集合框架笔记45:Set集合之TreeSet存储自定义对象并遍历练习1(自然排序:Comparable)

    1. 自然排序: TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按照升序排列,这种方式就是自然排序. Java中提供了一个Comp ...

  6. Java基础知识强化之集合框架笔记44:Set集合之TreeSet保证元素唯一性和自然排序的原理和图解

    1. TreeSet保证元素唯一性和自然排序的原理和图解 2. TreeSet唯一性以及有序性底层剖析: 通过观察TreeSet的add()方法,我们知道最终要看TreeMap的put()方法. 跟踪 ...

  7. Java基础知识强化之多线程笔记06:Lock接口 (区别于Synchronized块)

    1. 简介 我们讲到了如何使用关键字synchronized来实现同步访问.本文我们继续来探讨这个问题,从Java 5之后,在java.util.concurrent.locks包下提供了另外一种方式 ...

  8. Java基础知识强化之多线程笔记05:Java中继承thread类 与 实现Runnable接口的区别

    1. Java中线程的创建有两种方式:  (1)通过继承Thread类,重写Thread的run()方法,将线程运行的逻辑放在其中. (2)通过实现Runnable接口,实例化Thread类. 2. ...

  9. Java基础知识强化之IO流笔记65:序列化流 和 反序列化流

    1. 什么是 序列化 和 反序列化 ?     序列化 (Serialization):将对象的状态信息转换为可以存储或传输的形式的过程.比如转化为二进制.xml.json等的过程. 在序列化期间,对 ...

随机推荐

  1. LintCode Two Strings Are Anagrams

    1. 把string变为char数组 2. 排序Arrays.sort() public class Solution { /** * @param s: The first string * @pa ...

  2. &lbrack;转载&rsqb;PO BO VO DTO POJO DAO概念及其作用

    原文链接:http://jeoff.blog.51cto.com/186264/88517/ POJO = pure old java object or plain ordinary java ob ...

  3. ArcGIS中的坐标系定义与转换 &lpar;转载)

    原文:ArcGIS中的坐标系定义与转换 (转载) 1.基准面概念:  GIS中的坐标系定义由基准面和地图投影两组参数确定,而基准面的定义则由特定椭球体及其对应的转换参数确定,因此欲正确定义GIS系统坐 ...

  4. 学习pthreads,多线程的创建和终止

    在多CPU多线程的编程中,通过作者的学习发现,pthreads的运用越来越广泛,它是线程的POSIX标准,定义了创建和操作线程的一整套API.环境的配置见上一篇博文,配置好环境后只需要添加#inclu ...

  5. PHP,PSR开发规范

    https://github.com/hfcorriez/fig-standards/tree/zh_CN/%E6%8E%A5%E5%8F%97 PSR-1-basic-coding-standard ...

  6. BEAM188简单应用

    目录 BEAM188简介 APDL应用实例 显示梁三维图 BEAM188简介 BEAM188-3D线性有限应变梁 Beam188 单元适合于分析从细长到中等粗短的梁结构,该单元基于铁木辛哥梁结构理论, ...

  7. js 拖拽 碰撞 &plus; 重力 运动

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  8. windows7下安装apache&plus;PHP5&period;3

    Apache+PHP 安装与配置 最近在学习PHP,所以就在windows7下安装了PHP的开发环境.之所以没有选择集成的软件,如WAMP.AppServ等套件,是为了单独安装这些软件,有助于更加深入 ...

  9. mysql出现1030 Got error 28 from storage engine解决方法

    今天自己用 tp 写的项目报错 查了下,是磁盘临时空间不够导致 查看 my.cnf 的 tmpdir,看下指向哪个目录,修改到有空间的目录 最后发现是/var/tmp/phd/log/daemons. ...

  10. PGM学习之六 从有向无环图(DAG)到贝叶斯网络(Bayesian Networks)

    本文的目的是记录一些在学习贝叶斯网络(Bayesian Networks)过程中遇到的基本问题.主要包括有向无环图(DAG),I-Maps,分解(Factorization),有向分割(d-Separ ...