【问题标题】:What is object serialization?什么是对象序列化?
【发布时间】:2010-10-01 15:58:08
【问题描述】:

“对象序列化”是什么意思?你能用一些例子解释一下吗?

【问题讨论】:

标签: java serialization object-serialization


【解决方案1】:

序列化是在内存中获取“活动”对象并将其转换为可以存储在某处(例如,在内存中、磁盘上)的格式,然后再“反序列化”回活动对象。

【讨论】:

    【解决方案2】:

    序列化是将对象的状态转换为位以便将其存储在硬盘上的过程。当您反序列化同一个对象时,它将在以后保留其状态。它让您无需手动保存对象的属性即可重新创建对象。

    http://en.wikipedia.org/wiki/Serialization

    【讨论】:

    • "...这样它就可以存储在硬盘上。"或通过二进制协议传输。
    【解决方案3】:

    序列化是将对象转换为一系列字节,以便可以轻松地将对象保存到持久存储或通过通信链路流式传输。然后可以对字节流进行反序列化 - 转换为原始对象的副本。

    【讨论】:

    • 这是强制性的吗?我必须在发送数据之前对其进行序列化吗?转换成什么格式?
    • @FranciscoCorralesMorales - 在幕后,所有数据在通过流发送之前都将被序列化。您需要做多少,以及它将采用什么格式,都取决于您使用的平台和库。
    • @FranciscoCorralesMorales 你怎么说?我的意思是你说格式取决于平台和库。我真的很想知道格式。
    • 它是否仅适用于对象?我们可以序列化变量(不使用对象声明)吗?
    • @Rumado 仅限对象
    【解决方案4】:

    序列化是将 Java 对象转换为字节数组,然后以保留状态再次转换回对象的过程。适用于各种事情,例如通过网络发送对象或将内容缓存到磁盘。

    从this short article which explains programming part of the process quite well 阅读更多内容,然后转到Serializable javadoc。您可能也有兴趣阅读this related question。

    【讨论】:

      【解决方案5】:

      您可以将序列化视为将对象实例转换为字节序列(取决于实现可能是二进制或非二进制)的过程。

      当您想通过网络传输一个对象数据时非常有用,例如从一个 JVM 到另一个。

      在Java中,序列化机制是内置在平台中的,但是需要实现Serializable接口才能使对象可序列化。

      您还可以通过将属性标记为 transient 来防止对象中的某些数据被序列化。

      最后你可以覆盖默认机制,并提供你自己的;这可能适用于某些特殊情况。为此,您可以使用hidden features in java 之一。

      重要的是要注意被序列化的是对象的“值”或内容,而不是类定义。因此方法没有被序列化。

      这是一个非常基本的示例,带有 cmets 以方便阅读:

      import java.io.*;
      import java.util.*;
      
      // This class implements "Serializable" to let the system know
      // it's ok to do it. You as programmer are aware of that.
      public class SerializationSample implements Serializable {
      
          // These attributes conform the "value" of the object.
      
          // These two will be serialized;
          private String aString = "The value of that string";
          private int    someInteger = 0;
      
          // But this won't since it is marked as transient.
          private transient List<File> unInterestingLongLongList;
      
          // Main method to test.
          public static void main( String [] args ) throws IOException  { 
      
              // Create a sample object, that contains the default values.
              SerializationSample instance = new SerializationSample();
      
              // The "ObjectOutputStream" class has the default 
              // definition to serialize an object.
              ObjectOutputStream oos = new ObjectOutputStream( 
                                     // By using "FileOutputStream" we will 
                                     // Write it to a File in the file system
                                     // It could have been a Socket to another 
                                     // machine, a database, an in memory array, etc.
                                     new FileOutputStream(new File("o.ser")));
      
              // do the magic  
              oos.writeObject( instance );
              // close the writing.
              oos.close();
          }
      }
      

      当我们运行这个程序时,文件“o.ser”被创建了,我们可以看到后面发生了什么。

      如果我们将 someInteger 的值更改为,例如 Integer.MAX_VALUE,我们可能会比较输出,看看有什么区别。

      这是一个精确显示差异的屏幕截图:

      你能看出其中的不同吗? ;)

      Java 序列化中还有一个相关字段:serialversionUID,但我想这已经太长了,无法涵盖它。

      【讨论】:

      • @raam86 instance 是被序列化的对象。你可能会认为 main 方法是一个单独的程序,它创建一个 SerializationSample 类型的对象
      • @raam86 是 main 方法中的第一条语句:SerializationSample instance = new SerializationSample(); 然后创建输出并将对象写入该输出。
      • 哦。没有跟得足够近。太好了!!
      • @jacktrades 你为什么不试试呢。只需复制/粘贴示例并看到“NotSerializableException”被抛出:)
      • @jacktrades 因为计算机没有被告知允许序列化对象:) oos 是什么意思?
      【解决方案6】:

      序列化意味着在java中持久化对象。如果你想保存对象的状态,以后想重建状态(可能在另一个JVM),可以使用序列化。

      请注意,对象的属性只会被保存。如果你想再次复活对象,你应该有类文件,因为只会存储成员变量而不是成员函数。

      例如:

      ObjectInputStream oos = new ObjectInputStream(                                 
                                       new FileInputStream(  new File("o.ser")) ) ;
      SerializationSample SS = (SearializationSample) oos.readObject();
      

      Searializable 是一个标记接口,它标记你的类是可序列化的。标记接口意味着它只是一个空接口,使用该接口会通知JVM这个类可以被序列化。

      【讨论】:

        【解决方案7】:

        我喜欢@OscarRyz 呈现的方式。虽然在这里我继续 story of serialization 最初由 @amitgupta 编写。

        即使知道机器人类结构并拥有序列化数据,地球科学家也无法反序列化可以使机器人工作的数据。

        Exception in thread "main" java.io.InvalidClassException:
        SerializeMe; local class incompatible: stream classdesc
        :
        

        火星的科学家们正在等待全额付款。付款完成后,火星的科学家与地球的科学家分享了@98​​7654322@。地球科学家将其设置为机器人类,一切都很好。

        【讨论】:

          【解决方案8】:

          序列化是将对象保存在存储介质(如文件或内存缓冲区)中或以二进制形式通过网络连接传输的过程。序列化的对象是独立于 JVM 的,并且可以被任何 JVM 重新序列化。在这种情况下,“内存中”的 java 对象状态被转换为字节流。用户无法理解这种类型的文件。它是一种特殊类型的对象,即被 JVM(Java 虚拟机)重用。这种序列化对象的过程也称为对对象进行压缩或编组。

          要序列化的对象必须实现java.io.Serializable接口。 对象的默认序列化机制写入对象的类、类签名以及所有非瞬态和非静态字段的值。

          class ObjectOutputStream extends java.io.OutputStream implements ObjectOutput,
          

          ObjectOutput 接口扩展了DataOutput 接口并添加了用于序列化对象和将字节写入文件的方法。 ObjectOutputStream 扩展 java.io.OutputStream 并实现 ObjectOutput 接口。它将对象、数组和其他值序列化为流。因此ObjectOutputStream的构造函数写为:

          ObjectOutput ObjOut = new ObjectOutputStream(new FileOutputStream(f));
          

          以上代码已用于使用ObjectOutputStream( ) 构造函数创建ObjectOutput 类的实例,该构造函数将FileOuputStream 的实例作为参数。

          ObjectOutput 接口通过实现ObjectOutputStream 类来使用。 ObjectOutputStream 被构造为序列化对象。

          在java中反序列化一个对象

          序列化的相反操作称为反序列化,即从一系列字节中提取数据称为反序列化,也称为膨胀或解组。

          ObjectInputStream 扩展 java.io.InputStream 并实现 ObjectInput 接口。它反序列化来自输入流的对象、数组和其他值。因此ObjectInputStream的构造函数写成:

          ObjectInputStream obj = new ObjectInputStream(new FileInputStream(f));
          

          程序的上述代码创建了ObjectInputStream 类的实例,以反序列化已由ObjectInputStream 类序列化的文件。上面的代码使用 FileInputStream 类的实例创建了实例,该类保存了必须反序列化的指定文件对象,因为 ObjectInputStream() 构造函数需要输入流。

          【讨论】:

            【解决方案9】:

            Java Object Serialization

            Serialization 是一种将 Java 对象图转换为字节数组进行存储(to disk file)或传输(across a network),然后通过使用反序列化的机制 我们可以还原对象图。 使用参考共享机制正确恢复对象图。 但在存储之前,请检查 input-file/network 中的 serialVersionUID 和 .class 文件 serialVersionUID 是否相同。如果没有,请发送java.io.InvalidClassException。

            每个版本化的类必须标识它能够为其写入流并从中读取的原始类版本。例如,版本化类必须声明:

            serialVersionUID 语法

            // ANY-ACCESS-MODIFIER static final long serialVersionUID = (64-bit has)L;
            private static final long serialVersionUID = 3487495895819393L;
            

            serialVersionUID 对序列化过程至关重要。但开发人员可以选择将其添加到 java 源文件中。如果未包含 serialVersionUID,则序列化运行时将生成一个 serialVersionUID 并将其与类关联。序列化的对象将包含此 serialVersionUID 以及其他数据。

            注意 - 强烈建议所有可序列化的类都显式声明一个 serialVersionUID,since the default serialVersionUID computation is highly sensitive to class details that may vary depending on compiler implementations,这样会导致反序列化过程中出现意外的 serialVersionUID 冲突,从而导致反序列化失败。

            Inspecting Serializable Classes


            Java 对象只能序列化。如果一个类或其任何超类实现了 java.io.Serializable 接口 或其子接口,java.io.Externalizable。

            • 一个类必须实现 java.io.Serializable 接口才能成功序列化其对象。 Serializable 是一个标记接口,用于通知编译器实现它的类必须添加可序列化行为。 这里 Java 虚拟机 (JVM) 负责其自动序列化。

              transient 关键字: java.io.Serializable interface

              在序列化对象时,如果我们不希望对象的某些数据成员被序列化,我们可以使用瞬态修饰符。瞬态关键字将阻止该数据成员被序列化。

              • 序列化过程会忽略声明为瞬态或静态的字段。

              TRANSIENT & VOLATILE

              +--------------+--------+-------------------------------------+
              |  Flag Name   |  Value | Interpretation                      |
              +--------------+--------+-------------------------------------+
              | ACC_VOLATILE | 0x0040 | Declared volatile; cannot be cached.|
              +--------------+--------+-------------------------------------+
              |ACC_TRANSIENT | 0x0080 | Declared transient; not written or  |
              |              |        | read by a persistent object manager.|
              +--------------+--------+-------------------------------------+
              
              class Employee implements Serializable {
                  private static final long serialVersionUID = 2L;
                  static int id;
              
                  int eno; 
                  String name;
                  transient String password; // Using transient keyword means its not going to be Serialized.
              }
              
            • 实现 Externalizable 接口允许对象完全控制对象的序列化形式的内容和格式。调用 Externalizable 接口的方法 writeExternal 和 readExternal 来保存和恢复对象状态。当由一个类实现时,它们可以使用 ObjectOutput 和 ObjectInput 的所有方法写入和读取自己的状态。对象负责处理发生的任何版本控制。

              class Emp implements Externalizable {
                  int eno; 
                  String name;
                  transient String password; // No use of transient, we need to take care of write and read.
              
                  @Override
                  public void writeExternal(ObjectOutput out) throws IOException {
                      out.writeInt(eno);
                      out.writeUTF(name);
                      //out.writeUTF(password);
                  }
                  @Override
                  public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
                      this.eno = in.readInt();
                      this.name = in.readUTF();
                      //this.password = in.readUTF(); // java.io.EOFException
                  }
              }
              
            • 只有支持 java.io.Serializable 或 java.io.Externalizable 接口的对象才能成为written to/read from 流。每个可序列化对象的类都被编码,包括类的类名和签名、对象的字段和数组的值,以及从初始对象引用的任何其他对象的闭包。

            文件的可序列化示例

            public class SerializationDemo {
                static String fileName = "D:/serializable_file.ser";
            
                public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
                    Employee emp = new Employee( );
                    Employee.id = 1; // Can not Serialize Class data.
                    emp.eno = 77;
                    emp.name = "Yash";
                    emp.password = "confidential";
                    objects_WriteRead(emp, fileName);
            
                    Emp e = new Emp( );
                    e.eno = 77;
                    e.name = "Yash";
                    e.password = "confidential";
                    objects_WriteRead_External(e, fileName);
            
                    /*String stubHost = "127.0.0.1";
                    Integer anyFreePort = 7777;
                    socketRead(anyFreePort); //Thread1
                    socketWrite(emp, stubHost, anyFreePort); //Thread2*/
            
                }
                public static void objects_WriteRead( Employee obj, String serFilename ) throws IOException{
                    FileOutputStream fos = new FileOutputStream( new File( serFilename ) );
                    ObjectOutputStream objectOut = new ObjectOutputStream( fos );
                    objectOut.writeObject( obj );
                    objectOut.close();
                    fos.close();
            
                    System.out.println("Data Stored in to a file");
            
                    try {
                        FileInputStream fis = new FileInputStream( new File( serFilename ) );
                        ObjectInputStream ois = new ObjectInputStream( fis );
                        Object readObject;
                        readObject = ois.readObject();
                        String calssName = readObject.getClass().getName();
                        System.out.println("Restoring Class Name : "+ calssName); // InvalidClassException
            
                        Employee emp = (Employee) readObject;
                        System.out.format("Obj[No:%s, Name:%s, Pass:%s]", emp.eno, emp.name, emp.password);
            
                        ois.close();
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
                public static void objects_WriteRead_External( Emp obj, String serFilename ) throws IOException {
                    FileOutputStream fos = new FileOutputStream(new File( serFilename ));
                    ObjectOutputStream objectOut = new ObjectOutputStream( fos );
            
                    obj.writeExternal( objectOut );
                    objectOut.flush();
            
                    fos.close();
            
                    System.out.println("Data Stored in to a file");
            
                    try {
                        // create a new instance and read the assign the contents from stream.
                        Emp emp = new Emp();
            
                        FileInputStream fis = new FileInputStream(new File( serFilename ));
                        ObjectInputStream ois = new ObjectInputStream( fis );
            
                        emp.readExternal(ois);
            
                        System.out.format("Obj[No:%s, Name:%s, Pass:%s]", emp.eno, emp.name, emp.password);
            
                        ois.close();
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
            

            可序列化的网络示例

            Distributing object 在不同地址空间中的状态,无论是在同一台计算机上的不同进程中,还是在通过网络连接的多台计算机中,但它们通过共享数据和调用方法一起工作。

            /**
             * Creates a stream socket and connects it to the specified port number on the named host. 
             */
            public static void socketWrite(Employee objectToSend, String stubHost, Integer anyFreePort) {
                try { // CLIENT - Stub[marshalling]
                    Socket client = new Socket(stubHost, anyFreePort);
                    ObjectOutputStream out = new ObjectOutputStream(client.getOutputStream());
                    out.writeObject(objectToSend);
                    out.flush();
                    client.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            // Creates a server socket, bound to the specified port. 
            public static void socketRead(  Integer anyFreePort ) {
                try { // SERVER - Stub[unmarshalling ]
                    ServerSocket serverSocket = new ServerSocket( anyFreePort );
                    System.out.println("Server serves on port and waiting for a client to communicate");
                        /*System.in.read();
                        System.in.read();*/
            
                    Socket socket = serverSocket.accept();
                    System.out.println("Client request to communicate on port server accepts it.");
            
                    ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
                    Employee objectReceived = (Employee) in.readObject();
                    System.out.println("Server Obj : "+ objectReceived.name );
            
                    socket.close();
                    serverSocket.close();
                } catch (IOException | ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
            

            @see

            【讨论】:

            • 当你为一个已经有几个很好答案的六年前的问题添加一个答案时,你需要做的比拼写错误的杂音要好得多。
            • @ejp Downvoting 是表达您的负面意见的工具。冒犯和粗鲁是不可接受的。
            • @KonstantinosChertouras 给出拒绝投票的理由对发帖者很有帮助,这些都是我的理由,不管你喜欢与否。
            • 您还需要避免错误,例如断言序列化具有安全目的。它没有。
            • @EJP 我更新了我的帖子,更正了序列化不是出于安全目的,而是用于将对象的状态转换为任何存储并使用恢复对象的原始状态SUID 通过反序列化机制。 JVM to JVM
            【解决方案10】:

            敢于回答6年前的问题,只是为Java新手增加了一个非常高层次的理解

            什么是序列化?

            将对象转换为字节

            什么是反序列化?

            将字节转换回对象(反序列化)。

            什么时候使用序列化?

            当我们想要持久化对象时。 当我们希望对象在 JVM 的生命周期之外存在时。

            现实世界的例子:

            ATM:当账户持有人尝试通过ATM从服务器取款时,账户持有人的取款明细等信息将被序列化并发送到服务器,该服务器对明细进行反序列化并用于执行操作。

            如何在 java 中执行序列化。

            1. 实现java.io.Serializable接口(标记接口,所以没有方法可以实现)。

            2. 持久化对象:使用java.io.ObjectOutputStream 类,一个过滤器流,它是低级字节流的包装器(将对象写入文件系统或通过网络线路传输扁平对象并在另一端重建) .

            • writeObject(&lt;&lt;instance&gt;&gt;) - 写一个对象
            • readObject() - 读取序列化对象

            记住:

            当你序列化一个对象时,只会保存对象的状态,不会保存对象的类文件或方法。

            当您序列化一个 2 字节的对象时,您会看到 51 字节的序列化文件。

            对象如何序列化和反序列化的步骤。

            答案:它是如何转换为 51 字节文件的?

            • 首先写入序列化流魔术数据(STREAM_MAGIC= "AC ED" 和 STREAM_VERSION=JVM 的版本)。
            • 然后它写出与实例关联的类的元数据(类的长度、类的名称、serialVersionUID)。
            • 然后它递归地写出超类的元数据,直到找到java.lang.Object。
            • 然后从与实例关联的实际数据开始。
            • 最后将与实例关联的对象的数据从元数据开始写入实际内容。

            编辑:参考link阅读。

            这将回答几个常见问题:

            1. 如何不序列化类中的任何字段。
              Ans: 使用瞬态关键字

            2. 当子类被序列化时,父类是否也被序列化?
              Ans: 不,如果父级没有扩展 Serializable 接口的父级字段,则不会被序列化。

            3. 当父类被序列化时,子类是否会被序列化?
              Ans: 是的,默认情况下子类也会被序列化。

            4. 如何避免子类被序列化?
              答:一个。覆盖 writeObject 和 readObject 方法并抛出 NotSerializableException。

              b.您也可以在子类中标记所有字段瞬态。

            5. 某些系统级类,例如 Thread、OutputStream 及其子类和 Socket,是不可序列化的。

            【讨论】:

            • 非常感谢您的简洁回答,非常有帮助!
            【解决方案11】:

            将文件作为对象返回:http://www.tutorialspoint.com/java/java_serialization.htm

                    import java.io.*;
            
                    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();
                          }
                       }
                    }
            
                import java.io.*;
                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;
                      }
                      System.out.println("Deserialized Employee...");
                      System.out.println("Name: " + e.name);
                      System.out.println("Address: " + e.address);
                      System.out.println("SSN: " + e.SSN);
                      System.out.println("Number: " + e.number);
                    }
                }
            

            【讨论】:

            • 这不能回答问题的“是什么”或“请解释”部分。
            【解决方案12】:

            我自己博客中的两分钱:

            Here is a detailed explanation of the Serialization:(我自己的博客)

            序列化:

            序列化是保持对象状态的过程。它以字节序列的形式表示和存储。这可以存储在文件中。从文件中读取对象状态并恢复它的过程称为反序列化。

            序列化需要什么?

            在现代架构中,总是需要存储对象状态然后检索它。例如在 Hibernate 中,要存储一个对象,我们应该使类 Serializable。它的作用是,一旦对象状态以字节的形式保存,它就可以传输到另一个系统,然后该系统可以从状态中读取并检索类。对象状态可以来自数据库或不同的 jvm 或来自单独的组件。在序列化的帮助下,我们可以检索对象状态。

            代码示例及说明:

            首先让我们看一下Item Class:

            public class Item implements Serializable{
            
                /**
                *  This is the Serializable class
                */
                private static final long serialVersionUID = 475918891428093041L;
                private Long itemId;
                private String itemName;
                private transient Double itemCostPrice;
                public Item(Long itemId, String itemName, Double itemCostPrice) {
                    super();
                    this.itemId = itemId;
                    this.itemName = itemName;
                    this.itemCostPrice = itemCostPrice;
                  }
            
                  public Long getItemId() {
                      return itemId;
                  }
            
                 @Override
                  public String toString() {
                      return "Item [itemId=" + itemId + ", itemName=" + itemName + ", itemCostPrice=" + itemCostPrice + "]";
                   }
            
            
                   public void setItemId(Long itemId) {
                       this.itemId = itemId;
                   }
            
                   public String getItemName() {
                       return itemName;
                   }
                   public void setItemName(String itemName) {
                        this.itemName = itemName;
                    }
            
                   public Double getItemCostPrice() {
                        return itemCostPrice;
                    }
            
                    public void setItemCostPrice(Double itemCostPrice) {
                         this.itemCostPrice = itemCostPrice;
                    }
            }
            

            在上面的代码中可以看出Item类实现了Serializable。

            这是使类可序列化的接口。

            现在我们可以看到一个名为 serialVersionUID 的变量被初始化为 Long 变量。这个数字是编译器根据类的状态和类属性计算出来的。这是帮助 jvm 在从文件中读取对象状态时识别对象状态的数字。

            为此,我们可以查看官方的 Oracle 文档:

            序列化运行时与每个可序列化类关联 版本号,称为serialVersionUID,用于 反序列化以验证序列化的发送者和接收者 对象已加载与该对象兼容的类 关于序列化。如果接收者已经为 对象的 serialVersionUID 与 对应的发件人的类,然后反序列化将导致 无效类异常。可序列化的类可以声明自己的 通过声明一个名为的字段显式 serialVersionUID "serialVersionUID" 必须是静态的、最终的并且是 long 类型: ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;如果一个 可序列化类没有显式声明一个serialVersionUID, 然后序列化运行时将计算一个默认值 该类的 serialVersionUID 值基于 类,如 Java(TM) 对象序列化中所述 规格。但是,强烈建议所有 可序列化的类显式声明 serialVersionUID 值,因为 默认的 serialVersionUID 计算对类高度敏感 细节可能因编译器实现而异,并且可以 因此导致意外的 InvalidClassExceptions 在 反序列化。因此,要保证一致的serialVersionUID 跨不同 java 编译器实现的值,一个可序列化的 类必须声明一个显式的 serialVersionUID 值。也是 强烈建议显式 serialVersionUID 声明使用 可能的情况下使用私有修饰符,因为此类声明仅适用于 立即声明类--serialVersionUID 字段不是 作为继承成员有用。

            如果您注意到我们使用了另一个关键字 transient。

            如果字段不可序列化,则必须将其标记为瞬态。这里我们将 itemCostPrice 标记为瞬态,并且不希望将其写入文件中

            现在让我们看看如何在文件中写入对象的状态,然后从那里读取它。

            public class SerializationExample {
            
                public static void main(String[] args){
                    serialize();
                   deserialize();
                } 
            
                public static void serialize(){
            
                     Item item = new Item(1L,"Pen", 12.55);
                     System.out.println("Before Serialization" + item);
            
                     FileOutputStream fileOut;
                     try {
                         fileOut = new FileOutputStream("/tmp/item.ser");
                         ObjectOutputStream out = new ObjectOutputStream(fileOut);
                         out.writeObject(item);
                         out.close();
                         fileOut.close();
                         System.out.println("Serialized data is saved in /tmp/item.ser");
                       } catch (FileNotFoundException e) {
            
                              e.printStackTrace();
                       } catch (IOException e) {
            
                              e.printStackTrace();
                       }
                  }
            
                public static void deserialize(){
                    Item item;
            
                    try {
                            FileInputStream fileIn = new FileInputStream("/tmp/item.ser");
                            ObjectInputStream in = new ObjectInputStream(fileIn);
                            item = (Item) in.readObject();
                            System.out.println("Serialized data is read from /tmp/item.ser");
                            System.out.println("After Deserialization" + item);
                    } catch (FileNotFoundException e) {
                            e.printStackTrace();
                    } catch (IOException e) {
                           e.printStackTrace();
                    } catch (ClassNotFoundException e) {
                           e.printStackTrace();
                    }
                 }
            }
            

            在上面我们可以看到一个对象的序列化和反序列化的例子。

            为此,我们使用了两个类。为了序列化我们使用了 ObjectOutputStream 的对象。我们已经使用 writeObject 方法将对象写入文件。

            对于反序列化,我们使用了从文件中读取对象的 ObjectInputStream。它使用 readObject 从文件中读取对象数据。

            上述代码的输出如下:

            Before SerializationItem [itemId=1, itemName=Pen, itemCostPrice=12.55]
            Serialized data is saved in /tmp/item.ser
            After DeserializationItem [itemId=1, itemName=Pen, itemCostPrice=null]
            

            请注意,来自反序列化对象的 itemCostPrice 是 null,因为它没有被写入。

            我们已经在本文的第一部分讨论了 Java 序列化的基础知识。

            现在让我们深入讨论它以及它是如何工作的。

            首先让我们从 serialversionuid 开始。

            serialVersionUID 在 Serializable 类中用作版本控制。

            如果您没有显式声明 serialVersionUID,JVM 会根据 Serializable 类的各种属性自动为您完成。

            Java 计算 serialversionuid 的算法(在此处阅读更多详细信息)

            1. 类名。
              1. 写成 32 位整数的类修饰符。
              2. 按名称排序的每个接口的名称。
              3. 对于按字段名称排序的类的每个字段(私有静态和私有瞬态字段除外:字段的名称。 字段的修饰符写为 32 位整数。描述符 领域的。
              4. 如果存在类初始值设定项,请写出以下内容:方法的名称,.
              5. 方法的修饰符 java.lang.reflect.Modifier.STATIC,写成 32 位整数。
              6. 方法的描述符,()V.
              7. 对于按方法名称和签名排序的每个非私有构造函数:方法的名称,.的修饰符 方法写为 32 位整数。方法的描述符。
              8. 对于按方法名称和签名排序的每个非私有方法:方法的名称。方法的修饰符写成 32 位整数。方法的描述符。
              9. SHA-1 算法在 DataOutputStream 生成的字节流上执行,并生成五个 32 位值 sha[0..4]。这 散列值由第一个和第二个 32 位值组合而成 SHA-1 消息摘要。如果消息摘要的结果,五个 32 位字 H0 H1 H2 H3 H4,位于一个由五个 int 值组成的数组中,名为 sha,哈希值的计算方式如下:
                long hash = ((sha[0] >>> 24) & 0xFF) |
            >            ((sha[0] >>> 16) & 0xFF) << 8 |
            >            ((sha[0] >>> 8) & 0xFF) << 16 |
            >            ((sha[0] >>> 0) & 0xFF) << 24 |
            >            ((sha[1] >>> 24) & 0xFF) << 32 |
            >            ((sha[1] >>> 16) & 0xFF) << 40 |
            >            ((sha[1] >>> 8) & 0xFF) << 48 |
            >        ((sha[1] >>> 0) & 0xFF) << 56;
            

            Java 的序列化算法

            序列化对象的算法描述如下:
            1. 它写出与实例关联的类的元数据。
            2.递归写出超类的描述,直到找到java.lang.object。
            3. 一旦完成元数据信息的写入,它就会从与实例关联的实际数据开始。但这一次,它 从最顶层的超类开始。
            4. 它递归地写入与实例关联的数据,从最小的超类开始到最派生的类。

            注意事项:

            1. 类中的静态字段不能被序列化。

              public class A implements Serializable{
                   String s;
                   static String staticString = "I won't be serializable";
              }
              
            2. 如果 read 类中的 serialversionuid 不同,它将引发 InvalidClassException 异常。

            3. 如果一个类实现了可序列化,那么它的所有子类也将是可序列化的。

              public class A implements Serializable {....};
              
              public class B extends A{...} //also Serializable
              
            4. 如果一个类有另一个类的引用,则所有引用都必须是可序列化的,否则将不会执行序列化过程。在这种情况下,NotSerializableException 在运行时被抛出。

            例如:

            public class B{
                 String s,
                 A a; // class A needs to be serializable i.e. it must implement Serializable
            }
            

            【讨论】:

            • '序列化是将对象的状态序列化的过程以字节序列的形式表示和存储'是没有意义的。如果serialVersionUID 不同,它将抛出InvalidClassException,而不是ClassCastException。没有必要浪费所有空间来重新指定serialVersionUID 计算。文档被引用,过长,但没有链接或正确引用。这里的绒毛太多,错误也太多。
            • '序列化是序列化的过程'仍然没有意义。
            【解决方案13】:

            |*|序列化类:将对象转换为字节并将字节转换回对象(反序列化)。

            class NamCls implements Serializable
            {
                int NumVar;
                String NamVar;
            }
            

            |=> 对象序列化是将对象的状态转换为字节流的过程。

            • |-> 当您希望对象在 JVM 的生命周期之外存在时实现。
            • |-> 序列化对象可以存储在数据库中。
            • |-> 可序列化对象无法被人类读取和理解,因此我们可以实现安全性。

            |=> 对象反序列化是获取对象状态并将其存储到对象(java.lang.Object)中的过程。

            • |-> 在存储其状态之前,它会检查输入文件/网络中的serialVersionUID 和.class 文件serialVersionUID 是否相同。
              &nbsp&nbsp如果不抛出 java.io.InvalidClassException.

            |=> Java 对象只有在其类或其任何超类时才可序列化

            • 实现 java.io.Serializable 接口或
            • 它的子接口 java.io.Externalizable。

            |=> 类中的静态字段无法序列化。

            class NamCls implements Serializable
            {
                int NumVar;
                static String NamVar = "I won't be serializable";;
            }
            

            |=> 如果您不想序列化类的变量,请使用瞬态关键字

            class NamCls implements Serializable
            {
                int NumVar;
                transient String NamVar;
            }
            

            |=> 如果一个类实现了可序列化,那么它的所有子类也将是可序列化的。

            |=> 如果一个类有另一个类的引用,所有的引用必须是可序列化的,否则序列化过程将不会执行。在这种情况下,
            NotSerializableException 在运行时被抛出。

            【讨论】:

              【解决方案14】:

              我将提供一个类比来帮助巩固对象序列化/反序列化的概念目的/实用性。

              我想对象序列化/反序列化是在试图通过雨水渠移动对象的情况下。对象本质上被“分解”或序列化 成更模块化的自身版本——在这种情况下,是字节序列——以便有效地被授予通过介质的通道。在计算意义上,我们可以将字节通过雨水渠的路径视为类似于通过网络的字节。我们正在改变我们的对象,以符合更理想的运输方式或格式。序列化的对象通常存储在二进制文件中,以后可以读取、写入或两者兼而有之。

              也许一旦我们的对象能够作为一个分解的字节序列从排水管中溜走,我们可能希望将该对象的表示作为二进制数据存储在数据库或硬盘驱动器中。不过,主要的收获是,通过序列化/反序列化,我们可以选择让我们的对象在序列化后保持二进制形式,或者通过执行反序列化“检索”对象的原始形式。

              【讨论】:

                猜你喜欢
                • 2010-11-21
                • 2010-10-21
                • 1970-01-01
                • 1970-01-01
                • 2012-01-27
                • 1970-01-01
                • 2011-04-13
                • 1970-01-01
                相关资源
                最近更新 更多