总结每天10问之序列化

本文围绕Java的序列化与反序列化展开,介绍了序列化是将数据转化为二进制流,反序列化则是其逆过程。还探讨了序列化不保存静态变量、序列化ID的作用、序列化的继承关系,以及使用序列化框架时的情况,同时提到transient关键字可阻止变量序列化。
1、什么是序列化?

答:

序列化我的理解是我们看到的数据转化成二进制bytes流向磁盘,网络。并经过磁盘、网络之后另外个时间点/另外一个操作系统进行反序列化,将二进制的bytes流翻译成我们能理解的数据格式;比如:json格式。

上面是大白话的回答:

下面是官方回答:

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

序列化不仅仅局限于语言,而是网络、操作系统方面的层次。

也可以从生命周期的角度理解:

JVM里面的数据都是随着JVM消失而消失的,如果我们需要数据比JVM的生命周期长,我们就可以为它实现一个序列化接口,让它可以保存到磁盘,或者发送到网络外面。

2、Java的序列化是什么样子?

答:

从第一问可以得知:序列化是对对象流的读写操作。大白话就是怎么将一个对象变成一串流。 Java为我们封装好了使用方法,我们只需要implements Serializable 即可。

如果需要更多的定制化内容,就需要重写 writeObject() 方法。而需要定制化反序列化需要重写 readObject()方法。

对象流在java中就是ObjectOutputStream,下面是一个ObjectOutputStream的使用案例:

@Test
public void test2() {
    String suffix = String.valueOf(Math.random());
    String rootPath = Thread.currentThread().getContextClassLoader().getResource("").toString().substring(6);
    List<File> files = new ArrayList<>(100);

    try {
        for (int i = 8; i < 108; i++) {
            String fileName = Integer.toBinaryString(Objects.hashCode(i));
            List<Comment> comments = new ArrayList<>(20);
            for (int j = 0; j < 20; j++) {
                Comment comment = new Comment();
                comment.setId(j);
                comment.setContent("Love " + fileName);
                comments.add(comment);
            }
            User user = new User();
            user.setId(i);
            user.setName(fileName);
            user.setPassword(Integer.toBinaryString(fileName.hashCode()));
            user.setComments(comments);
            File file = File.createTempFile(fileName, suffix, new File(rootPath));
            try (FileInputStream fis = new FileInputStream(file);
                 FileOutputStream fos = new FileOutputStream(file, true);
                ) {

                ObjectOutputStream oos = new ObjectOutputStream(fos);
                oos.writeObject(user);
                ObjectInputStream ois = new ObjectInputStream(fis);
                User object = (User) ois.readObject();
                System.out.println(object);
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        files.forEach(File::deleteOnExit);
    }
}

private static class Comment  implements Serializable {
    private int id;
    private String content;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return "Comment{" +
            "id=" + id +
            ", content='" + content + '\'' +
            '}';
    }
}

private static class User implements Serializable {
    private int id;
    private String name;
    private String password;
    private List<Comment> comments;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public List<Comment> getComments() {
        return comments;
    }

    public void setComments(List<Comment> comments) {
        this.comments = comments;
    }

    @Override
    public String toString() {
        return "User{" +
            "id=" + id +
            ", name='" + name + '\'' +
            ", password='" + password + '\'' +
            ", comments=" + comments +
            '}';
    }
}

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

3、什么是反序列化?Java的反序列化

答:

反序列化就是序列化的反过程,指的是从磁盘/网络的流(bytes)回到内存,怎么恢复我们认识的样子。

Java的反序列化也是需要实现Serializable的,然后使用输入流,将bytes放入内存。案例可以看第2问的代码。

定制化反序列化需要重写 readObject()方法。

4、序列化/放序列化不会保存/解析静态变量?

答:

是的,序列化会忽略静态变量,这是jdk考虑一般静态变量是共同拥有的不是这个类独有的,所以不会将其序列化,不过使用者可以重写writeObject(),将静态变量写入磁盘/流向网络。

5、序列化 ID (serialVersionUID)为什么有的地方有,有的地方没有?

答:

虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID)。

serialVersionUID其实是一个对象的身份证,可能在负载的网络交互过程中,存在着两个很类似的对象(类名,字段大多相等),但是两个是不同的对象。这个时候程序就需要使用serialVersionUID去做区分。

如果改动serialVersionUID,就会出现反序列化异常的情况。

比如:

public class SerializableDemo1 {   
    public static void main(String[] args) {
        //Initializes The Object      
       User1 user = new User1();
        user.setName("hollis");
       //Write Obj to File
       ObjectOutputStream oos = null;
       try {
            oos = new ObjectOutputStream(new FileOutputStream("tempFile"));                  oos.writeObject(user);
       } catch (IOException e) {
            e.printStackTrace();
        } finally {
           IOUtils.closeQuietly(oos);
        }
   }
}
class User1 implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    public String getName() {
       return name;
    } 
  public void setName(String name) {
        this.name = name;
    }
 }

修改一下User1 类,把 serialVersionUID 的值改为 2L。

class User1 implements Serializable {
    private static final long serialVersionUID = 2L;

然后执行以下代码,把文件中的对象反序列化出来:

public class SerializableDemo2 {
   public static void main(String[] args) {
       //Read Obj from File
       File file = new File("tempFile");
        ObjectInputStream ois = null;
       try {
            ois = new ObjectInputStream(new FileInputStream(file));
            User1 newUser = (User1) ois.readObject();
            System.out.println(newUser);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(ois);
            try {
               FileUtils.forceDelete(file);
            } catch (IOException e) {
               e.printStackTrace();
            }
       }
    }
 }

最后运行出现如下异常:

java.io.InvalidClassException: serializablation.User1; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2

这是因为,在进行反序列化时,JVM会把传来的字节流中的 serialVersionUID 与本地相应实体类的 serialVersionUID 进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException。

上面说明了serialVersionUID的作用,但是为什么有的地方有,有的地方没有呢?

我们试验一段代码:

去掉上面代码的序列化id

class User1 implements Serializable {
    private String name;
    public String getName() {
       return name;
    } 
  public void setName(String name) {
        this.name = name;
    }
 }

运行保存

加上一个字段,运行加载

class User1 implements Serializable {
    //private static final long serialVersionUID = 2L;
    
    private String name;
    private int 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;
    }
}

运行的结果为:

java.io.InvalidClassException: self.donghao.experiments.serializablation.User1; local class incompatible: stream classdesc serialVersionUID = -3952380580598475573, local class serialVersionUID = 1397259157723020558

从这里可以看出,系统自己添加了一个 serialVersionUID。

强烈建议序列化的对象一定要加上serialVersionUID

可以使用IDEA根据类名、接口名、成员方法及属性等来生成一个 64 位的哈希字段,比如:
private static final long serialVersionUID = xxxxL;

6、序列化的继承关系

答:

序列化如果需要父类也序列化,就需要父类也implements Serializable

7、使用了序列化框架,还需要序列化吗?

答:

看情况,比如我们使用了fastjson框架,发现序列化/反序列化一切正常,数据也是随意乱改程序也没异常,其实这只是http协议帮我们把数据可视化了。在rmi/dubbo等rpc网络调用中,还是需要序列化的,序列化id。如果是http协议,确实是不需要我们自己implements Serializable ,json框架也会帮我们构建对象的流。所以使用者需要根据自己的情况去使用序列化。

8、Java中transient 关键字可以阻止该变量被序列化到文件中

答:

字段使用transient声明了之后,就不会序列化到文件/网络之中。

在变量声明前加上 transient 关键字,可以阻止该变量被序列化到文件中,在被反序列化后, transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

<think>好的,我现在要详细分析用户的题:Java中数组对象和String对象的序列化与反序列化操作。首先,我得回顾一下Java序列化的基本概念,确保自己理解正确。序列化是将对象转换为字节流的过程,以便存储或传输,反序列化则是将字节流恢复为对象的过程。 接下来,需要明确数组对象和String对象在序列化中的特殊性。String是Java中的一个不可变类,实现了Serializable接口,所以它本身就可以被序列化。数组的话,如果是基本类型数组,比如int[],或者对象数组,比如String[],它们的序列化处理可能有所不同。需要确认数组是否自动支持序列化,或者是否需要额外处理。 然后,考虑具体的实现步骤。对于数组对象,可能需要确保数组中的每个元素都是可序列化的。例如,如果数组里包含自定义对象,这些对象必须实现Serializable接口,否则会抛出NotSerializableException。而String本身已经实现了Serializable,所以String数组应该可以直接序列化。 接下来是序列化和反序列化的代码示例。需要用ObjectOutputStream和ObjectInputStream来进行操作。对于数组,可能需要将其转换为字节数组输出流,再写入文件或网络。而String的处理类似,但要注意字符编码的题吗?不过序列化可能已经处理了编码,所以不需要额外步骤。 还要注意异常处理,比如IOException和ClassNotFoundException。在反序列化时,需要正确转换类型,可能需要强制类型转换,比如将反序列化后的对象转为String[]或者String。 另外,可能需要提到serialVersionUID的题,但对于String和数组,可能不需要显式声明,因为它们是Java标准库中的类,其serialVersionUID由JVM管理。不过如果用户自定义了数组中的元素类型,则需要注意这一点。 可能存在的误区是,用户可能认为所有数组都自动可序列化,但如果数组元素不可序列化,就会失败。需要提醒这一点。比如,如果有一个Object[]数组,里面包含非序列化的对象,序列化时会出错。 总结步骤:首先确保对象可序列化,然后使用ObjectOutputStream写入,ObjectInputStream读取,处理异常,类型转换。对于数组和String,流程基本一致,但要注意元素的可序列化性。 最后,验证示例代码的正确性,确保语法正确,比如使用try-with-resources处理流,正确关闭资源,避免内存泄漏。同时,检查是否有中文注释或说明,符合用户的要求。 现在需要将以上思考整理成结构化的回答,分点说明,确保清晰易懂,符合用户提到的帮助用户逐步解决题,并保证真实可靠。</think>在Java中,序列化是将对象转换为字节流的过程,反序列化则是将字节流恢复为对象。以下是针对数组对象和`String`对象的操作详解: --- ### 一、序列化基础 - **前提条件**:对象必须实现`java.io.Serializable`接口(标记接口,无方法)。 - **核心类**: - `ObjectOutputStream`:将对象写入字节流(序列化)。 - `ObjectInputStream`:从字节流恢复对象(反序列化)。 --- ### 二、String对象的序列化与反序列化 `String`类已实现`Serializable`,可直接操作。 #### 示例代码 ```java import java.io.*; public class StringSerialization { public static void main(String[] args) { String original = "Hello, 世界!"; // 序列化 try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("string.ser"))) { oos.writeObject(original); // 写入String对象 } catch (IOException e) { e.printStackTrace(); } // 反序列化 try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("string.ser"))) { String deserialized = (String) ois.readObject(); // 强制类型转换 System.out.println(deserialized); // 输出: Hello, 世界! } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } } ``` --- ### 三、数组对象的序列化与反序列化 数组本身隐式实现了`Serializable`,但需确保**数组元素可序列化**。 #### 1. 基本类型数组(如`int[]`) 可直接序列化: ```java int[] numbers = {1, 2, 3}; // 序列化 try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("array.ser"))) { oos.writeObject(numbers); } // 反序列化 try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("array.ser"))) { int[] deserializedNumbers = (int[]) ois.readObject(); System.out.println(Arrays.toString(deserializedNumbers)); // 输出: [1, 2, 3] } ``` #### 2. 对象数组(如`String[]`) 若元素可序列化,则数组可序列化: ```java String[] names = {"Alice", "Bob"}; // 序列化与反序列化流程同上 // 反序列化后强制转换: String[] deserializedNames = (String[]) ois.readObject(); ``` #### 3. 自定义对象数组 若数组元素为自定义类,该类必须实现`Serializable`: ```java class Person implements Serializable { private String name; // 构造方法、getter/setter省略 } Person[] people = {new Person("Alice"), new Person("Bob")}; // 序列化与反序列化流程同上 ``` --- ### 四、注意事项 1. **不可序列化的元素**:若数组元素未实现`Serializable`,会抛出`NotSerializableException`。 2. **版本一致性**:序列化与反序列化的类需兼容(`serialVersionUID`一致)。 3. **敏感数据**:避免序列化敏感字段,可用`transient`关键字标记。 --- ### 五、总结 - **String对象**:直接序列化,无额外要求。 - **数组对象**:需确保元素类型可序列化,其他操作与普通对象一致。 - **通用步骤**: 1. 使用`ObjectOutputStream`写入对象。 2. 使用`ObjectInputStream`读取并强制类型转换。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值