一.序列化与反序列化
1.序列化的概念
将数据结构或对象转换成二进制串的过程。
2.反序列化的概念
将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程。
3.常见的序列化
json、xml、protobuff等
4.如何选择序列化方案
1.可读性
2.健壮性
3.性能
4.可扩展性等
5.serializable的使用
1.基本使用
1.通常我们会给需要序列化的对象加上一个serialVersionUID用来标记版本;
2.transient关键字可以标记属性不会被序列化,将其置为null或者0;
//1.通过Serializable接口实现序列化
// 通常我们会给需要序列化的对象加上一个serialVersionUID用来标记版本
// 使得双方使用同一个版本,防止一方修改。
static class User implements Serializable{
private final static long serialVersionUID=1000L;
@Override
public String toString() {
return "User{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
//持久化对象写入到本地
@Test
public void testWriteSerializable() throws IOException {
User user= new User("小明", 14);
ObjectOutputStream objectOutputStream=
new ObjectOutputStream(new FileOutputStream(new File("a.out")));
objectOutputStream.writeObject(user);
objectOutputStream.close();
}
//读取本地数据到对象
@Test
public void testReadSerializable() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream=
new ObjectInputStream(new FileInputStream(new File("a.out")));
User user = (User) objectInputStream.readObject();
objectInputStream.close();
System.out.println(user);
}
@Test
public void testSerializable() throws IOException, ClassNotFoundException {
//将对象转成字节流并存放在userData中去
byte[] userData=null;
User user= new User("小明", 14);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream=
new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(user);
userData=byteArrayOutputStream.toByteArray();
byteArrayOutputStream.close();
objectOutputStream.close();
//将字节流转成User对象
ObjectInputStream objectInputStream=
new ObjectInputStream(new ByteArrayInputStream(userData));
User user2 = (User) objectInputStream.readObject();
objectInputStream.close();
System.out.println(user2);
}
3.如果父类没有实现序列化,子类实现序列化并且想序列化父类的属性,可以通过添加方法将父类属性写入进去。
static class SonUser extends ParentUser implements Serializable {
private final static long serialVersionUID = 1000L;
@Override
public String toString() {
return "User{" +
"name='" + name + ''' +
", age=" + age +
",parentName=" + getParentName() +
'}';
}
private String name;
private int age;
public SonUser(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//将父类属性序列化
private void writeObject(ObjectOutputStream objectOutputStream) throws IOException {
objectOutputStream.defaultWriteObject();
objectOutputStream.writeObject(getParentName());
}
//将父类对象反序列化
private void readObject(ObjectInputStream objectInputStream) throws ClassNotFoundException, IOException {
objectInputStream.defaultReadObject();
setParentName((String) objectInputStream.readObject());
}
}
static class ParentUser {
public String getParentName() {
return parentName;
}
public void setParentName(String parentName) {
this.parentName = parentName;
}
@Override
public String toString() {
return "ParentUser{" +
"parentName='" + parentName + ''' +
'}';
}
private String parentName;
}
@Test
public void testSerializableExtends() throws IOException, ClassNotFoundException {
//将对象转成字节流并存放在userData中去
byte[] userData = null;
SonUser user = new SonUser("小明", 14);
user.setParentName("老王"); //设置父类
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream =
new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(user);
userData = byteArrayOutputStream.toByteArray();
byteArrayOutputStream.close();
objectOutputStream.close();
//将字节流转成User对象
ObjectInputStream objectInputStream =
new ObjectInputStream(new ByteArrayInputStream(userData));
SonUser user2 = (SonUser) objectInputStream.readObject();
objectInputStream.close();
System.out.println(user2);
}
4.如果基类被序列化,如何避免子类被序列化
方案1:可以使用transient关键字;
方案2:重写writeObject和readObject方法
//如何避免子类被序列化
static class FatherUser implements Serializable {
}
static class SonUser2 extends FatherUser {
private String sonName;
public String getSonName() {
return sonName;
}
public void setSonName(String sonName) {
this.sonName = sonName;
}
public SonUser2( String sonName) {
this.sonName = sonName;
}
private void writeObject(ObjectOutputStream out) throws IOException {
// 不进行序列化操作
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
// 不进行反序列化操作
}
@Override
public String toString() {
return "SonUser2{" +
"sonName='" + sonName + ''' +
'}';
}
}
@Test
public void testSerializableExtends2() throws IOException, ClassNotFoundException {
//将对象转成字节流并存放在userData中去
byte[] userData = null;
SonUser2 user = new SonUser2("小明");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream =
new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(user);
userData = byteArrayOutputStream.toByteArray();
byteArrayOutputStream.close();
objectOutputStream.close();
//将字节流转成User对象
ObjectInputStream objectInputStream =
new ObjectInputStream(new ByteArrayInputStream(userData));
SonUser2 user2 = (SonUser2) objectInputStream.readObject();
objectInputStream.close();
System.out.println(user2);
}
5.注意关于枚举的序列化和反序列化都是同一个对象,只是在内存中拿到序列化对象‘
6.序列化和单例:我们可以通过在单例类中重写readResolve方法(obj.readResolve会在其他read方法之前调用)来解决。(如果反射破坏单例,可以使用静态变量来控制)
import java.io.*;
// 单例类
class Singleton implements Serializable {
private static final long serialVersionUID = 1L;
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
// 重写 readResolve 方法
private Object readResolve() {
return instance;
}
}
public class DeserializeSingletonSolution {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 序列化单例对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
oos.writeObject(Singleton.getInstance());
oos.close();
// 反序列化单例对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.ser"));
Singleton deserializedSingleton = (Singleton) ois.readObject();
ois.close();
// 比较原单例对象和反序列化后的对象
System.out.println(Singleton.getInstance() == deserializedSingleton);
}
}
2.ExSerializable的使用
1.需要writeExternal和readExternal中字节写 顺序要一致
2.必须要一个无参构造参数,不然会抛出异常。
//2.通过Externalizable实现序列化
// 1.需要writeExternal和readExternal中字节写 顺序要一致
// 2.必须要一个无参构造参数,不然会抛出异常。
static class NewUser implements Externalizable{
public NewUser() {
}
@Override
public String toString() {
return "User{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
private String name;
private int age;
public NewUser(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws ClassNotFoundException, IOException {
name= (String) in.readObject();
age=in.readInt();
}
}
@Test
public void testExSerializable() throws IOException, ClassNotFoundException {
//将对象转成字节流并存放在userData中去
byte[] userData=null;
NewUser user= new NewUser("小明", 14);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream=
new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(user);
userData=byteArrayOutputStream.toByteArray();
byteArrayOutputStream.close();
objectOutputStream.close();
//将字节流转成User对象
ObjectInputStream objectInputStream=
new ObjectInputStream(new ByteArrayInputStream(userData));
NewUser user2 = (NewUser) objectInputStream.readObject();
objectInputStream.close();
System.out.println(user2);
}
3.序列化的基本流程
5.parcelable的使用(android独有)
1.在内存中序列化;
2.主要在进程间通讯;
3.数据大小由限制,一般不超过1M,可以修改内核为4M。
二.知识点
1.反序列化后的对象,需要调用构造函数重新构造吗?
Serializable 反序列化不调用构造函数,而 Parcelable 反序列化会调用自定义的构造函数。
2.序列前的对象和序列化后的对象是什么关系?
除了枚举 都是深拷贝。
3.Android中为什么要设计出Bundle而不是直接用Map结构?
Bundle底层使用ArrayMap,比起map结构,它的效率更高。
4.serialVersionUID的作用?
版本控制和标记
5.Android中Intent/Bundle的通信原理及大小限制?
通信原理
在 Android 中, Intent ****里的 ****Bundle ****采用 Binder 机制来传送数据。当使用 ****Intent ****启动其他组件时,它会离开当前应用程序进程,进入 ****ActivityManagerService ****进程。由于 Android 基于 Linux 系统,不同进程之间无法直接传输 Java 对象,因此需要对对象进行序列化,以此实现对象在应用程序进程和 ****ActivityManagerService ****进程之间的传输。
大小限制
能使用的 Binder 缓冲区存在大小限制,不同机型和 ROM 该限制有所不同,有些手机是 1MB,有些是 2MB,这个限制定义在 frameworks /native/libs/binder/processState.cpp 类中。并且一个进程默认有 16 个 Binder 线程,所以单个线程能占用的缓冲区更小,大约一个线程可占用 128KB 。当传递的数据过大时,系统会报 TransactionTooLargeException 错误,错误描述通常为 “The Binder transaction failed because it was too large”。
6.为什么Intent不能直接再组件间传递对象而通过序列化?
intent交互是进程间通讯,为了满足进程间通讯。
7.序列化和持久化的关系和区别?
三.json与gson
1.json的数据格式
json(javascript object notation)是一种轻量级数据交换格式。用来标记、存储、传输数据的。
json的基本语法
json主要数据有对象和数组, J语法是 JavaScript 对象表示法的子集,其核心规则包括:使用大括号表示对象,中括号表示数组,所有键必须用双引号包裹,数据类型包括 字符串、数字、布尔值、null、对象和数组 ,并且这些类型可以嵌套使用以表达复杂的数据结构。
2.gson的使用
1.对象和json的相互转换
private Gson gson=new Gson();
//对象转换
@Test
public void testUser2Json(){
String json = gson.toJson(new UserGson("张三", 15));
System.out.println(json);
}
//json转object
@Test
public void testJson2User(){
String json="{"name":"张三","age":16}";
UserGson userGson = gson.fromJson(json, UserGson.class);
System.out.println(userGson);
}
2.数组和json的相互转换
//数组转换
@Test
public void testUsers2Json(){
UserGson zhansan = new UserGson("张三", 15);
UserGson lisi = new UserGson("李四", 16);
List<UserGson> userGsons=new ArrayList<>();
userGsons.add(zhansan);
userGsons.add(lisi);
String json = gson.toJson(userGsons);
//没有键的数组
// [{"name":"张三","age":15},{"name":"李四","age":16}]
System.out.println(json);
//有键的数组
//{"users":[{"name":"张三","age":15},{"name":"李四","age":16}]}
Map<String,List<UserGson>> mapUserJson=new HashMap<>();
mapUserJson.put("users",userGsons);
String json2 = gson.toJson(mapUserJson);
System.out.println(json2);
//[1,2]
int i[]={1,2};
String json3 = gson.toJson(i);
System.out.println(json3);
Integer[] integers = gson.fromJson(json3, Integer[].class);
System.out.println("IntArray:"+Arrays.toString(integers));
}
3.特殊的使用
1.对象属性中的键注解
2.对象属性是否序列化的注解
@JsonAdapter(UserGsonAnnotationAdapter.class)//使用特定适配器解析
public class UserGsonAnnotation {
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public UserGsonAnnotation(String name, int age,int idx) {
this.name = name;
this.age = age;
this.idx=idx;
}
@Override
public String toString() {
return "UserGsonAnnotation{" +
"name='" + name + ''' +
", age=" + age +
", idx=" + idx +
'}';
}
@SerializedName("userName") //序列化和反序列化时的名字
@Expose
private String name;
@SerializedName(value = "userAge",alternate = {"userAGE","USERage"}) //默认时userAge,如果没有看看userAGE/USERage
@Expose
private int age;
@Expose(serialize = false,deserialize = false)//表示是否需要序列化或者反序列化 默认为true
private int idx;
public int getIdx() {
return idx;
}
public void setIdx(int idx) {
this.idx = idx;
}
}
//根据expose注解来设置是否要序列化或者反序列化某个属性
@Test
public void testJsonSpecialUser() {
//需要使用 GsonBuilder 并调用 excludeFieldsWithoutExposeAnnotation() 方法,
// 这样 Gson 才会只处理带有 @Expose 注解的字段,注意,如果这样创建gson的话
// 其他属性也要配置此注解,如果不配置,则不序列化
gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
String json = "{"userName":"张三","userAge":16}";
UserGsonAnnotation userGson = gson.fromJson(json, UserGsonAnnotation.class);
System.out.println(userGson);
}
3.可序列化null的配置
//容许对象出现null
//{"name":null,"age":15}
//UserGson{name='null', age=15}
@Test
public void testGsonNull() {
gson = new GsonBuilder()
.serializeNulls()//序列化支持为值null
.create();
UserGson userGson1 = new UserGson(null, 15);
String json = gson.toJson(userGson1);
System.out.println(json);
UserGson userGson = gson.fromJson(json, UserGson.class);
System.out.println(userGson);
System.out.println("--------------");
//反序列化支持null
gson = new GsonBuilder()
.registerTypeAdapter(UserGson.class,
new JsonDeserializer<UserGson>() {
@Override
public UserGson deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
JsonElement nameElement = jsonObject.get("name");
String name = null;
if (nameElement != null && !nameElement.isJsonNull()) {
name = nameElement.getAsString();
}
JsonElement ageElement = jsonObject.get("age");
int age = 0;
if (ageElement != null && !ageElement.isJsonNull()) {
try {
age = ageElement.getAsInt();
} catch (NumberFormatException e) {
// 处理年龄字段不是有效整数的情况
throw new JsonParseException("Invalid age value", e);
}
}
UserGson userGson = new UserGson(name, age);
System.out.println("user:" + userGson);
return userGson;
}
}).create();
System.out.println("json:==="+json);
UserGson userGson2 = gson.fromJson(json, UserGson.class);
System.out.println(userGson2);
}
4.配置自定义适配器
public class UserGsonAnnotationAdapter extends TypeAdapter<UserGsonAnnotation> {
@Override
public void write(JsonWriter out, UserGsonAnnotation value) throws IOException {
if (null == value) {
System.out.println("write null--->>>");
out.nullValue();
return;
}
//序列化时
out.beginObject();
out.name("userName").value(value.getName() + ".myAdapter");
out.name("userAge").value(value.getAge());
// out.name("idx").value(-1);
out.endObject();
System.out.println("write---->>>>");
}
//反序列化
@Override
public UserGsonAnnotation read(JsonReader in) throws IOException {
String name = null;
int age = 0;
in.beginObject();
while (in.hasNext()) {
String fieldName = in.nextName();
if (fieldName.equals("userName")) {
if (in.peek() != JsonToken.NULL) {
name = in.nextString();
} else {
in.nextNull();
}
} else if (fieldName.equals("userAge")) {
if (in.peek() != JsonToken.NULL) {
age = in.nextInt();
} else {
in.nextNull();
}
}
}
in.endObject();
System.out.println("read---->>>>");
return new UserGsonAnnotation(name, age, -99);
}
}
//使用自定义Adapter来解析UserGsonAnnotation
//方式1:在创建adapter时配置
//方式2:在需要使用自定义adapter上加上注解
@Test
public void testGson() {
gson = new GsonBuilder()
.setPrettyPrinting()//设置json格式
.serializeNulls()
//.registerTypeAdapter(UserGsonAnnotation.class, new UserGsonAnnotationAdapter())
.create();
UserGsonAnnotation userGson1 = new UserGsonAnnotation(null, 15, 1);
String json = gson.toJson(userGson1);
System.out.println("json:" + json);
System.out.println("-------------->>>>");
UserGsonAnnotation userGson = gson.fromJson(json, UserGsonAnnotation.class);
System.out.println(userGson);
}
5.
3.gson是如何克服泛型擦除的?
TypeToken ****能够克服 Java 的泛型擦除问题,其核心原理是 利用匿名内部类的字节码中保留了编译时的泛型信息 。
在 Java 中,虽然运行时会进行类型擦除(Type Erasure),即泛型信息在编译后会被移除,例如 List<String> 和 List<Integer> 在运行时都变成了原始类型 List ,导致无法通过常规反射获取其具体的泛型参数 [1] 。然而,Java 语言规范规定: 当一个类继承了一个带有泛型的父类时,这个泛型的具体类型信息会被保留在子类的字节码的常量池中 。
TypeToken ****正是巧妙地利用了这一特性:
- 创建匿名子类 :当你编写 ****
new TypeToken<List<String>>() {}****时,你实际上是在创建 ****TypeToken****的一个匿名子类,并且明确指定了其泛型参数为 ****List<String>。 - 信息被固化 :尽管 ****
TypeToken****本身是一个泛型类,在运行时也会被擦除,但这个新创建的匿名子类的超类(即 ****TypeToken<List<String>>)的完整泛型信息会被 JVM 写入该匿名类的 ****.class****文件中。 - 运行时提取:
TypeToken的构造函数(在匿名子类实例化时被调用)会立即执行getClass().getGenericSuperclass()这个反射方法。这个方法能够返回当前类(匿名子类)的直接父类的Type对象,而这个Type对象准确地反映了源代码中使用的实际类型参数,即ParameterizedType: List<String>。 - 提供类型令牌:提取到的完整
Type信息会被保存在TypeToken实例中,并通过getType()方法暴露给外部。Gson 等库就可以使用这个精确的类型信息来指导反序列化过程,从而正确地将 JSON 数组解析成List<String>,而不是默认的List<Object>或LinkedTreeMap。
简而言之, TypeToken 通过“让一个子类去记住父类的泛型”这一技巧,绕过了泛型擦除的限制,为需要精确泛型信息的场景(如 Gson 的反序列化)提供了关键支持 。
4.Gson知识点
1.JsonElement
对应每个json结点,如jsonArray、jsonObject、JsonNull、JsonPrimitive
2.TypeToke
封装类型信息,包括泛型参数,用于唯一标识类型
3.TypeAdapter
抽象类型适配器,将数据/对象转换成对应的类型的对象/数据,子类实现有基本数据类型的适配器。
4.面试题