序列化和编组的区别是什么?

708

我知道在几种分布式技术中(如RPC),术语“Marshaling”被使用,但不明白它与序列化有何区别。它们不都是将对象转换为一系列比特吗?

相关链接:

什么是序列化?

什么是对象编组?

13个回答

535

在远程过程调用的背景下,Marshaling和Serialization是松散的同义词,但从意图上看它们有语义上的不同。

特别地,Marshaling是关于如何将参数从这里传递到那里,而Serialization是关于将结构化数据复制到或从原始形式(例如字节流)中。在这个意义上,Serialization通常实现值传递语义,是执行Marshaling的一种方式之一。

同时,一个对象也可以通过引用来被Marshaled,此时“在线”上的数据仅仅是原始对象的位置信息。然而,这样的对象可能仍然适用于值Serialization。

正如@Bill提到的那样,还可能存在其他元数据,例如代码库位置甚至对象实现代码。


4
有没有一个词可以同时表示串行化和反串行化?需要一个包含这些方法的接口名称。 - raffian
1
@raffian,您是指由进行序列化和反序列化的对象实现的接口,还是由负责管理该过程的对象实现的接口?我建议使用“Serializable”和“Formatter”这两个关键词;必要时添加前导I、更改大小写等。 - Jeffrey Hantin
26
在电信行业中,我们称将数据串行化和反序列化的组件为"SerDes"或"serdes",通常发音为sir-dez或sir-deez,具体取决于个人偏好。我认为它类似于"调制解调器"(即"Modulator-Demodulator")的构造。 - davidA
6
@naki,这是整个行业通用的术语 - 如果您查看高速FPGA数据表,它们将提到SERDES功能,尽管这些数据表都是相当现代的,可以追溯到1990年代。Google [NGrams](https://books.google.com/ngrams/graph?content=serdes&case_insensitive=on&year_start=1800&year_end=2000&corpus=15&smoothing=3)表明它在1980年代变得更为流行,尽管我确实在IBM数据表中找到了一个1970年的实例。[1970](https://books.google.com/books?id=iNItAQAAIAAJ&q=%22serdes%22&dq=%22serdes%22&hl=en&sa=X&ved= 0ahUKEwjxicDlxMfjAhUKVH0KHZxfAugQ6AEIKzAB) - davidA
Serdes看起来还不错,但我不会犹豫从Java中汲取灵感。可序列化的数据必须标记为瞬态数据,因此transienter似乎也是可行的 :) - Krepsy 3
显示剩余4条评论

308

这两者都有一个共同点 - 也就是将对象进行序列化。序列化用于对象的传输或存储。但是:

  • 序列化:当您对一个对象进行序列化时,只会将该对象中的成员数据写入字节流中;而不是实际实现该对象的代码。
  • 编组:在我们谈到将对象传递给远程对象(RMI)时,使用术语编组。在编组期间,对象被序列化(成员数据被序列化)+代码库被附加。

因此,序列化是编组的一部分。

CodeBase是提供接收者该对象实现位置信息的信息。任何认为可能会将对象传递给之前未见过该对象的另一个程序的程序必须设置代码库,以便接收者可以知道从哪里下载代码(如果没有本地可用的代码)。接收者将在对对象进行反序列化后从中获取代码库并从该位置加载代码。


68
好的,我会尽力进行翻译。下面是需要翻译的内容:+1 对于在这个上下文中定义 CodeBase 的含义。对于在这个上下文中定义 CodeBase 的含义表示赞同并支持对CodeBase进行解释说明。 - Omar Salem
3
不使用序列化进行编组确实会发生。请参见Swing的invokeAndWait和Forms的Invoke,它们将同步调用编组到UI线程,而无需涉及序列化。 - Jeffrey Hantin
2
“not the code that actually implements the object” 的意思是指并非实现对象的代码。这句话的含义是指,它不是实际执行对象功能的代码。您需要进一步了解上下文才能确定它所指的具体内容,例如它可能指的是类方法或其他相关代码。 - Vishal Anand
3
您的意思是“该对象的实现”是什么?您能举一个具体的“序列化”和“编组”的例子吗? - Cloud
1
在某些情况下,例如在函数调用在单个进程中在线程模型之间传递控制流时(例如,在共享线程池和仅限于单个固定线程的库之间),会发生无需序列化的编排操作。这就是为什么我说它们在远程过程调用(RPC)的上下文中是松散同义词的原因。 - Jeffrey Hantin

121

Marshalling (computer science)维基百科文章中:

在Python标准库中,“marshal”这个术语被认为是与“serialize”同义的1,但是在与Java相关的RFC 2713中,这两个术语并不是同义词:

对于一个对象,“marshal”的意思是记录它的状态和代码库,使得当“unmarshal”这个已编组的对象时,可以通过自动加载对象的类定义来获得原始对象的副本。您可以编组任何可序列化或远程的对象。编组类似于序列化,只是编组还记录了代码库。“Marshal”和序列化不同之处在于它特别处理远程对象。(RFC 2713)

“Serialize”一个对象意味着将其状态转换为字节流,以便可以将字节流转换回对象的副本。

所以,编组还会将对象的代码库保存在字节流中,除了它的状态。


2
如果一个对象在反序列化后只有状态,没有代码库,也就是说它的任何函数都不能被调用,那么你的意思是这个对象只是一种结构化数据类型。而如果同一个对象被编组,那么它将具有其代码库以及结构,可以调用其函数? - bjan
19
“Codebase”并不是真正意义上的“代码”。它是指使用远程类加载语义的程序如何找到新类。当对象发送方将对象序列化以传输到另一个JVM时,它会用称为代码库的信息对字节流进行注释。这些信息告诉接收方可以在哪里找到这个对象的实现。代码库注释中实际存储的信息是一组URL,可以从这些URL下载所需对象的类文件。 - Giuseppe Bertone
2
@Neurone 那个定义是特定于Jini和RMI的。 "Codebase"是一个通用术语。http://en.wikipedia.org/wiki/Codebase - Bill the Lizard
2
@BilltheLizard 没错,但因为你谈论的是Java中的marshalling,所以说将序列化和marshalling的区别归结为“marshalling除了保存对象状态,还保存了对象代码”的说法是错误的,这也引起了bjan的问题。Marshalling除了保存对象状态外,还保存了“代码库”。 - Giuseppe Bertone

31

基础知识

字节流 - 流是一系列数据。输入流 - 从源读取数据。输出流 - 将数据写入目的地。 Java字节流用于逐字节(每次8位)执行输入/输出操作。字节流适用于处理原始数据,如二进制文件。 Java字符流用于逐个读取/写入2个字节,因为在Java中使用Unicode约定存储字符,每个字符需要2个字节。字符流在处理(读取/写入)文本文件时很有用。

RMI(远程方法调用) - 是提供在Java中创建分布式应用程序机制的API。RMI允许对象在另一个JVM中运行时调用该对象上的方法。


序列化和编组都可以松散地用作同义词。以下是一些区别。

序列化 - 对象的数据成员被写入二进制形式或字节流(然后可以写入文件/内存/数据库等)。一旦对象数据成员被写入二进制形式,就无法保留关于数据类型的信息。

输入图像说明

Marshalling(编组)是将对象序列化(以二进制格式转换为字节流),并附加数据类型和代码库,然后传递给远程对象(RMI)的过程。Marshalling会将数据类型转换为预定命名规则,以便可以根据初始数据类型重构它。

enter image description here

因此,序列化是Marshalling的一个部分。

CodeBase(代码库)是一条信息,告诉接收Object的程序这个对象的实现可在何处找到。任何可能向另一个之前未见过该对象的程序传递对象的程序都必须设置代码库,以便接收者可以知道从哪里下载代码(如果没有本地可用的代码)。接收方将在反序列化对象时从中获取代码库,并从该位置加载代码。(摘自@Nasir的回答)

Serialization(序列化)几乎就像是对象使用的内存的“愚蠢”内存转储,而Marshalling(编组)存储有关自定义数据类型的信息。

在某种程度上,序列化执行按值传递的实现的Marshalling,因为没有传递数据类型的信息,只有原始形式被传递到字节流中。

如果数据流从一个操作系统传输到另一个操作系统,由于不同的操作系统可能使用不同的方式表示同一数据,因此序列化可能涉及大端和小端等问题。另一方面,尽管结果是更高级别的表示形式,但编组在不同操作系统之间迁移时完全没有问题。


1
感谢您的澄清和图示。 - brohjoe

20

我认为主要的区别在于Marshalling(编组)据说还涉及代码库。换句话说,您不能将对象编组和解组为不同类的状态等效实例。

序列化仅意味着您可以存储对象并重新获取等效状态,即使它是另一个类的实例。

话虽如此,它们通常是同义词。


2
如果您说的是一个对象,如果未序列化,则只能具有状态,没有任何代码库,即其任何函数都无法调用,它只是一种结构化数据类型。而且,如果对同一对象进行编组,则它将具有其代码库以及结构,并且可以调用其函数? - bjan

20

Marshaling是将函数的签名和参数转换为单个字节数组的过程。 这是专门为了RPC而进行的。

Serialization更常指将整个对象/对象树转换成字节数组。 Marshaling将序列化对象参数,以便将它们添加到消息中并通过网络传递。*Serialization也可用于存储到磁盘中.*


16

Marshalling是告诉编译器如何在另一个环境/系统上表示数据的规则;例如:

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string cFileName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public string cAlternateFileName;

正如您所看到的,两个不同的字符串值表示为不同的值类型。

序列化仅会转换对象的内容,而不是表示(将保持不变),并遵守序列化规则(导出什么或不导出)。例如,私有值将不会被序列化,公共值会被序列化,并且对象结构将保持不变。


9

以下是更具体的两个例子:

序列化示例:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

typedef struct {
    char value[11];
} SerializedInt32;

SerializedInt32 SerializeInt32(int32_t x) 
{
    SerializedInt32 result;
    
    itoa(x, result.value, 10);

    return result;
}

int32_t DeserializeInt32(SerializedInt32 x) 
{
    int32_t result;
    
    result = atoi(x.value);
    
    return result;
}

int main(int argc, char **argv)
{    
    int x;   
    SerializedInt32 data;
    int32_t result;
    
    x = -268435455;
    
    data = SerializeInt32(x);
    result = DeserializeInt32(data);
    
    printf("x = %s.\n", data.value);
    
    return result;
}

在序列化中,数据会被压缩成一种可以存储和之后解压的方式。
示例代码如下:
(MarshalDemoLib.cpp)
#include <iostream>
#include <string>

extern "C"
__declspec(dllexport)
void *StdCoutStdString(void *s)
{
    std::string *str = (std::string *)s;
    std::cout << *str;
}

extern "C"
__declspec(dllexport)
void *MarshalCStringToStdString(char *s)
{
    std::string *str(new std::string(s));
    
    std::cout << "string was successfully constructed.\n";
    
    return str;
}

extern "C"
__declspec(dllexport)
void DestroyStdString(void *s)
{
    std::string *str((std::string *)s);
    delete str;
    
    std::cout << "string was successfully destroyed.\n";
}

(MarshalDemo.c)

#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main(int argc, char **argv)
{
    void *myStdString;

    LoadLibrary("MarshalDemoLib");
    
    myStdString = ((void *(*)(char *))GetProcAddress (
        GetModuleHandleA("MarshalDemoLib"),
        "MarshalCStringToStdString"
    ))("Hello, World!\n");
    
    ((void (*)(void *))GetProcAddress (
        GetModuleHandleA("MarshalDemoLib"),
        "StdCoutStdString"
    ))(myStdString);

    ((void (*)(void *))GetProcAddress (
        GetModuleHandleA("MarshalDemoLib"),
        "DestroyStdString"
    ))(myStdString);    
}

在编组中,数据不一定需要被压平,但需要转换为另一种替代表示形式。所有类型转换都是编组,但并非所有编组都是类型转换。

编组不需要涉及动态分配,它也可以只是结构之间的转换。例如,您可能拥有一个 pair,但函数期望 pair 的第一个和第二个元素相反;您将一个 pair 转换到另一个 pair(使用 casting/memcpy)将不能完成工作,因为 fst 和 snd 会翻转。

#include <stdio.h>

typedef struct {
    int fst;
    int snd;
} pair1;

typedef struct {
    int snd;
    int fst;
} pair2;

void pair2_dump(pair2 p)
{
    printf("%d %d\n", p.fst, p.snd);
}

pair2 marshal_pair1_to_pair2(pair1 p)
{
    pair2 result;
    result.fst = p.fst;
    result.snd = p.snd;
    return result;
}

pair1 given = {3, 7};

int main(int argc, char **argv)
{    
    pair2_dump(marshal_pair1_to_pair2(given));
    
    return 0;
}

当你开始处理许多类型的标记联合时,编组的概念变得尤其重要。例如,您可能会发现很难让JavaScript引擎为您打印“c字符串”,但可以要求它为您打印包装后的c字符串。或者,如果您想从JavaScript运行时在Lua或Python运行时打印字符串。它们都是字符串,但通常没有编组就无法相处。
我最近遇到的一个烦恼是,JScript数组作为“__ComObject”从C#传输,并且没有记录如何操作此对象的方式。我可以找到它所在的地址,但实际上我不知道它的任何其他信息,因此真正弄清楚它的唯一方法是以任何可能的方式尝试对其进行探索,并希望找到有用的信息。因此,更容易创建一个具有友好接口的新对象(例如Scripting.Dictionary),将JScript数组对象中的数据复制到其中,并将该对象传递给C#,而不是使用JScript的默认数组。
(test.js)
var x = new ActiveXObject('Dmitry.YetAnotherTestObject.YetAnotherTestObject');
    
x.send([1, 2, 3, 4]);

(YetAnotherTestObject.cs)

using System;
using System.Runtime.InteropServices;

namespace Dmitry.YetAnotherTestObject
{
    [Guid("C612BD9B-74E0-4176-AAB8-C53EB24C2B29"), ComVisible(true)]
    public class YetAnotherTestObject
    {
        public void send(object x)
        {
            System.Console.WriteLine(x.GetType().Name);
        }
    }
}

上面的输出是“__ComObject”,从C#的角度来看,这在某种程度上是黑匣子。

另一个有趣的概念是,您可能已经了解如何编写代码,并且拥有知道如何执行指令的计算机,因此作为程序员,您有效地将您希望计算机执行的内容的概念从大脑传递到程序映像。如果我们有足够好的marshaller,则可以只想到要做/更改的事情,程序就会以这种方式更改,而无需在键盘上输入。因此,如果您有一种方法在几秒钟内将所有物理变化存储在大脑中,那么您可以将该数据编组成打印分号的信号,但那是极端的。

4

我对编组的理解与其他答案不同。

序列化:

利用某种约定,生成或恢复对象图的线格式版本。

编组:

利用映射文件生成或还原对象图的线格式版本,以便结果可以自定义。该工具可能开始遵循一项约定,但重要的区别在于能够自定义结果。

基于契约的开发:

在基于契约的开发环境中,编组非常重要。

  • 可以在保持外部接口稳定的情况下对内部对象图进行更改。这样,所有服务订阅者就不必为每个琐碎的更改而做修改。
  • 可以将结果映射到不同的语言。例如,从一种语言的属性名称约定(“property_name”)到另一种语言(“propertyName”)。

1
// 请问 @JasperBlues 在这个回答中所说的 "rehydrate" 具体是什么意思?我猜想它不仅仅适用于宇航员食品。 - Nathan Basanese
1
根据这个答案 - https://dev59.com/32w05IYBdhLWcg3w11W0#6991192 - (重新)水化的定义包含以下内容:水化一个对象是指获取一个存在于内存中但尚未包含任何领域数据(“真实”数据)的对象,然后用领域数据(例如来自数据库、网络或文件系统)填充它。 - pawel-schmidt

4

Marshalling通常在相对紧密相关的进程之间进行;而序列化并不一定具有这种期望。因此,当在进程之间进行数据马歇尔操作时,您可能只希望发送一个指向可能昂贵的数据的引用以便恢复,而在序列化时,您将希望保存所有数据,以便在反序列化时正确地重新创建对象。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接