什么是对象序列化?

别说谁变了你拦得住时间么 提交于 2020-02-26 02:23:30

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


#1楼

序列化是将对象保存在存储介质(例如文件或内存缓冲区)中或以二进制形式通过网络连接传输对象的过程。 序列化的对象独立于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的实例作为参数。

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

在Java中反序列化对象

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

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

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

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


#2楼

Java 对象序列化

Serialization是一种将Java对象图转换为字节数组以便存储( to disk file )或across a network传输( across a 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冲突,从而导致反序列化失败。

检查可序列化的类


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

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

    瞬态关键字: java.io.Serializable interface

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

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

    瞬态挥发性

    +--------------+--------+-------------------------------------+ | 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流中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();
        }
    }
}

网络上的可序列化示例

将对象的状态分布在不同的地址空间中,或者在同一台计算机上的不同进程中,甚至在通过网络连接的多台计算机中,都可以通过共享数据和调用方法来协同工作。

/**
 * 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();
    }
}

@看到


#3楼

敢于回答6岁的问题,对Java初学者来说只是一个非常高级的了解

什么是序列化?

将对象转换为字节,然后将字节转换回对象(反序列化)。

什么时候使用序列化?

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

真实示例:

ATM:当帐户持有人尝试通过ATM从服务器提取资金时,帐户持有人信息(例如提款详细信息)将被序列化并发送到服务器,在服务器上将详细信息反序列化并用于执行操作。

如何在Java中执行序列化。

  1. 实现java.io.Serializable接口(标记接口,因此没有实现方法)。

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

    • writeObject(<<instance>>) -写入对象
    • readObject() -读取序列化的对象

记得:

序列化对象时,将仅保存对象的状态,而不保存对象的类文件或方法。

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

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

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

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

如果您对有关Java序列化的部门信息感兴趣,请检查此链接

编辑 :一个更好的阅读链接

这将回答一些常见问题:

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

  2. 当子类被序列化时,父类会被序列化吗?
    回答:否,如果父级未扩展Serializable interface parent字段不会被序列化。

  3. 父级序列化后,子类会序列化吗?
    回答:是的,默认情况下,子类也被序列化。

  4. 如何避免子类被序列化?
    答: 重写writeObject和readObject方法,并抛出NotSerializableException

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

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

#4楼

将文件作为对象返回: 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);
        }
    }

#5楼

我自己博客中的两分钱:

这是序列化的详细说明 :(我自己的博客)

序列化:

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

序列化有什么需要?

在现代体系结构中,始终需要存储对象状态然后再检索它。 例如在Hibernate中,要存储对象,我们应该使类Serializable。 它的作用是,一旦对象状态以字节形式保存,就可以将其转移到另一个系统,该系统可以从状态中读取并检索类。 对象状态可以来自数据库或其他jvm,也可以来自单独的组件。 借助序列化,我们可以检索对象状态。

代码示例和说明:

首先让我们看一下Item类:

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从文件中读取对象状态时,该数字将帮助jvm识别对象状态。

为此,我们可以看一下正式的Oracle文档:

序列化运行时与每个可序列化的类关联一个版本号,称为serialVersionUID,在反序列化期间使用该版本号来验证序列化对象的发送者和接收者是否已加载了该对象的与序列化兼容的类。 如果接收方已为该对象加载了一个与相应发送方类具有不同的serialVersionUID的类,则反序列化将导致InvalidClassException。 可序列化的类可以通过声明一个名为“ serialVersionUID”的字段来显式声明其自己的serialVersionUID,该字段必须是静态的,最终的且类型为long:ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L; 如果可序列化的类未明确声明serialVersionUID,则序列化运行时将根据该类的各个方面为该类计算默认的serialVersionUID值,如Java(TM)对象序列化规范中所述。 但是,强烈建议所有可序列化的类显式声明serialVersionUID值,因为默认的serialVersionUID计算对类详细信息高度敏感,而类详细信息可能会根据编译器的实现而有所不同,因此可能在反序列化期间导致意外的InvalidClassExceptions。 因此,为了保证不同Java编译器实现之间的serialVersionUID值一致,可序列化的类必须声明一个显式的serialVersionUID值。 还强烈建议显式serialVersionUID声明在可能的情况下使用private修饰符,因为此类声明仅适用于立即声明的类-serialVersionUID字段作为继承成员没有用。

如果您发现有另一个关键字我们使用过,则它是瞬时的

如果字段不可序列化,则必须将其标记为瞬态。 在这里,我们将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]

请注意,反序列化对象中的itemCostPricenull,因为它没有被写入。

在本文的第一部分中,我们已经讨论了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]。 哈希值由SHA-1消息摘要的第一和第二个32位值组成。 如果消息摘要的结果(五个32位字H0 H1 H2 H3 H4)位于五个名为sha的int值的数组中,则哈希值的计算方式如下:
    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. 如果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
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!