Soy ingeniero de software y después de una discusión con algunos colegas, me di cuenta de que no tengo una buena comprensión del concepto de serialización. Según tengo entendido, la serialización es el proceso de convertir alguna entidad, como un objeto en OOP, en una secuencia de bytes, de modo que dicha entidad pueda almacenarse o transmitirse para su posterior acceso (el proceso de "deserialización").
El problema que tengo es: ¿no están todas las variables (ya sean primitivas int
u objetos compuestos) ya representadas por una secuencia de bytes? (Por supuesto que sí, porque están almacenados en registros, memoria, disco, etc.)
Entonces, ¿qué hace que la serialización sea un tema tan profundo? Para serializar una variable, ¿no podemos simplemente tomar estos bytes en la memoria y escribirlos en un archivo? ¿Qué complejidades me he perdido?
4 bytes
en mi PDP-11 y luego intento leer esos mismos cuatro bytes en la memoria de mi macbook, no son el mismo número (debido a Endianes). Por lo tanto, debe normalizar los datos a una representación que pueda descodificar (esto es serialización). La forma en que serializa los datos también tiene compensaciones de velocidad / flexibilidad humana / máquina legible.Respuestas:
Si tiene una estructura de datos complicada, su representación en la memoria normalmente podría estar dispersa por toda la memoria. (Piense en un árbol binario, por ejemplo).
Por el contrario, cuando desea escribirlo en el disco, probablemente desee tener una representación como una secuencia (con suerte corta) de bytes contiguos. Eso es lo que la serialización hace por ti.
fuente
Considere un gráfico de objeto en C con nodos definidos como este:
En tiempo de ejecución, todo el objeto
Node
gráfico del se dispersaría por el espacio de memoria, y el mismo nodo podría apuntar desde muchos nodos diferentes.No puede simplemente volcar la memoria en un archivo / secuencia / disco y llamarlo serializado porque los valores del puntero (que son direcciones de memoria) no se pueden deserializar (porque esas ubicaciones de memoria podrían estar ocupadas cuando carga el volcado de nuevo) en la memoria). Otro problema con simplemente volcar la memoria es que terminarás almacenando todo tipo de datos irrelevantes y espacio no utilizado: en x86 un proceso tiene hasta 4GiB de espacio de memoria, y un sistema operativo o MMU solo tiene una idea general de qué memoria es realmente significativo o no (basado en las páginas de memoria asignadas a un proceso), por lo que tener
Notepad.exe
volcar 4 GB de bytes sin procesar en mi disco cada vez que quiero guardar un archivo de texto parece un poco inútil.Otro problema es con el control de versiones: ¿qué sucede si serializa su
Node
gráfico el día 1 y luego el día 2 agrega otro campo aNode
(como otro valor de puntero o un valor primitivo), luego el día 3 des-serializa su archivo de ¿día 1?También debes considerar otras cosas, como el endianness. Una de las razones principales por las que los archivos MacOS e IBM / Windows / PC eran incompatibles entre sí en las décadas de 1980 y 1990 a pesar de que aparentemente los hicieron los mismos programas (Word, Photoshop, etc.) fue porque en valores enteros de múltiples bytes x86 / PC se guardaron en orden little-endian, pero en orden big-endian en Mac, y el software no se creó teniendo en cuenta la portabilidad multiplataforma. Hoy en día las cosas mejoran gracias a una mejor educación para desarrolladores y a nuestro mundo informático cada vez más heterogéneo.
fuente
El truco ya está descrito en la propia palabra: " serial ización".
La pregunta es básicamente: ¿cómo puedo representar una gráfica dirigida cíclica interconectada arbitrariamente compleja de objetos complejos arbitrariamente como una secuencia lineal de bytes?
Piénselo: una secuencia lineal es algo así como un gráfico dirigido degenerado donde cada vértice tiene exactamente un borde entrante y saliente (excepto el "primer vértice" que no tiene borde entrante y el "último vértice" que no tiene borde saliente) . Y un byte es obviamente menos complejo que un objeto .
Por lo tanto, parece razonable que a medida que se pasa de un gráfico arbitrariamente complejo a una mucho más restringida "graph" (en realidad sólo una lista) y de los objetos arbitrariamente complejas a simples bytes, la información de voluntad perderá, si hacemos esto ingenuamente y no lo hacemos ' t codifica la información "extraña" de alguna manera. Y eso es exactamente lo que hace la serialización: codificar la información compleja en un formato lineal simple.
Si está familiarizado con YAML , puede echar un vistazo a las características de ancla y alias que le permiten representar la idea de que "el mismo objeto puede aparecer en diferentes lugares" en una serialización.
Por ejemplo, si tiene el siguiente gráfico:
Podría representar eso como una lista de rutas lineales en YAML como esta:
También puede representarlo como una lista de adyacencia, o una matriz de adyacencia, o como un par cuyo primer elemento es un conjunto de nodos y cuyo segundo elemento es un conjunto de pares de nodos, pero en todas esas representaciones, debe tener una forma de referirse hacia atrás y hacia adelante a los nodos existentes , es decir, punteros , que generalmente no tiene en un archivo o una secuencia de red. Todo lo que tienes, al final, son bytes.
(Lo que por cierto significa que el archivo de texto YAML anterior también debe ser "serializado", para eso están las diversas codificaciones de caracteres y formatos de transferencia Unicode ... no es estrictamente "serialización", solo codificación, porque el archivo de texto ya es una serie / lista lineal de puntos de código, pero puede ver algunas similitudes).
fuente
Las otras respuestas ya abordan gráficos de objetos complejos, pero vale la pena señalar que la serialización de primitivas tampoco es trivial.
Usando nombres de tipo primitivo C para concreción, considere:
Yo serializo a
long
. Algún tiempo después, lo deserialicé, pero ... en una plataforma diferente, y ahoralong
está enint64_t
lugar delint32_t
que almacené. Por lo tanto, debo tener mucho cuidado con el tamaño exacto de cada tipo que almaceno o almacenar algunos metadatos que describan el tipo y el tamaño de cada campo.Tenga en cuenta que esta plataforma diferente podría ser la misma plataforma después de una compilación futura.
Yo serializo un
int32_t
. Algún tiempo después, lo des-serializo, pero ... en una plataforma diferente, y ahora el valor está corrupto. Lamentablemente guardé el valor en una plataforma big-endian, y lo cargué en una plataforma little-endian. Ahora necesito establecer una convención para mi formato o agregar más metadatos que describan la duración de cada archivo / secuencia / lo que sea. Y, por supuesto, realmente realiza las conversiones apropiadas.char
UTF-8 y unawchar_t
y UTF-16.Por lo tanto, afirmaría que la serialización de calidad razonable no es trivial incluso para las primitivas en la memoria contigua. Hay muchas decisiones de codificación que necesita documentar o describir con metadatos en línea.
Los gráficos de objetos agregan otra capa de complejidad además de eso.
fuente
Hay múltiples aspectos:
Legibilidad por el mismo programa
Su programa ha almacenado sus datos de alguna manera como bytes en la memoria. Pero podría estar disperso arbitrariamente en diferentes registros, con punteros yendo y viniendo entre sus piezas más pequeñas [editar: Como se comentó, físicamente los datos son más probables en la memoria principal que un registro de datos, pero eso no elimina el problema del puntero] . Solo piense en una lista entera vinculada. Cada elemento de la lista puede almacenarse en un lugar totalmente diferente y todo lo que mantiene la lista unida son los punteros de un elemento al siguiente. Si tomara esos datos tal como están e intente copiarlos en otra máquina que ejecute el mismo programa, tendría problemas:
Legibilidad por otro programa
Supongamos que logra asignar las direcciones correctas en otra máquina, para que sus datos encajen. Si sus datos son procesados por un programa separado en esa máquina (idioma diferente), ese programa podría tener una comprensión básica de los datos totalmente diferente. Supongamos que tiene objetos C ++ con punteros, pero su idioma de destino ni siquiera admite punteros en ese nivel. Una vez más, terminas sin una forma limpia de abordar esos datos en el segundo programa. Termina con algunos datos binarios en la memoria, pero luego, necesita escribir código adicional que envuelva los datos y de alguna manera los traduzca en algo con lo que su idioma de destino pueda trabajar. Suena como deserialización, solo que su punto de partida ahora es un objeto extraño disperso por su memoria principal, que es diferente para diferentes idiomas de origen, en lugar de un archivo con una estructura bien definida. Lo mismo, por supuesto, si intenta interpretar directamente el archivo binario que incluye punteros: debe escribir analizadores para cada forma posible en que otro idioma pueda representar datos en la memoria.
Legibilidad por un humano
Dos de los lenguajes de serialización modernos más destacados para la serialización basada en web (xml, json) son fácilmente entendibles por un humano. En lugar de una pila binaria de sustancia pegajosa, la estructura y el contenido reales de los datos son claros, incluso sin un programa para leer los datos. Esto tiene múltiples ventajas:
fuente
Además de lo que han dicho las otras respuestas:
A veces quieres serializar cosas que no son datos puros.
Por ejemplo, piense en un identificador de archivo o una conexión a un servidor. Aunque el identificador de archivo o el socket es un
int
, este número no tiene sentido la próxima vez que se ejecute el programa. Para recrear correctamente los objetos que contienen identificadores para tales cosas, debe volver a abrir archivos y volver a crear conexiones, y decidir qué hacer si esto falla.Actualmente, muchos idiomas admiten el almacenamiento de funciones anónimas dentro de objetos, por ejemplo, un
onBlah()
controlador en Javascript. Esto es desafiante porque dicho código puede contener referencias a datos adicionales que a su vez necesitan ser serializados. (Y luego está el problema de serializar código de una manera multiplataforma, que obviamente es más fácil para los idiomas interpretados). Aún así, incluso si solo se puede admitir un subconjunto del idioma, aún puede resultar bastante útil. No muchos mecanismos de serialización intentan serializar el código, pero consulte serialize-javascript .En los casos en que desea serializar un objeto pero contiene algo que no es compatible con su mecanismo de serialización, debe volver a escribir el código de una manera que funcione alrededor de esto. Por ejemplo, puede usar enumeraciones en lugar de funciones anónimas cuando hay un número finito de funciones posibles.
A menudo, desea que los datos serializados sean concisos.
Si envía datos a través de la red o incluso los almacena en el disco, puede ser importante mantener el tamaño pequeño. Una de las formas más fáciles de lograr esto es desechar la información que se puede reconstruir (por ejemplo, descartar cachés, tablas hash y representaciones alternativas de los mismos datos).
Por supuesto, el programador debe seleccionar manualmente lo que se va a guardar y lo que se debe descartar, y asegurarse de que las cosas se reconstruyan cuando se recrea el objeto.
Piensa en el acto de guardar un juego. Los objetos pueden contener muchos punteros a datos gráficos, datos de sonido y otros objetos. Pero la mayoría de estas cosas se pueden cargar desde los archivos de datos del juego y no es necesario almacenarlas en un archivo guardado. Descartarlo puede ser laborioso, por lo que a menudo se dejan pequeñas cosas. He editado hexadecimalmente algunos archivos guardados en mi tiempo y descubrí datos que eran claramente redundantes, como descripciones textuales de elementos.
A veces el espacio no es importante, pero la legibilidad sí lo es, en cuyo caso puede usar un formato ASCII (posiblemente JSON o XML).
fuente
Definamos qué es realmente una secuencia de bytes. Una secuencia de bytes consiste en un número entero no negativo llamado longitud y alguna función / correspondencia arbitraria que mapea cualquier número entero i que sea al menos cero y menor que la longitud a un valor de byte (un entero de 0 a 255).
Muchos de los objetos con los que trata en un programa típico no tienen esa forma, porque los objetos en realidad están compuestos de muchas asignaciones de memoria diferentes que están en diferentes lugares en la RAM, y podrían estar separados unos de otros por millones de bytes de cosas que no me importa Solo piense en una lista vinculada básica: cada nodo en la lista es una secuencia de bytes, sí, pero los nodos están en muchas ubicaciones diferentes en la memoria de su computadora, y están conectados con punteros. O simplemente piense en una estructura simple que tenga un puntero a una cadena de longitud variable.
La razón por la que queremos serializar estructuras de datos en una secuencia de bytes es generalmente porque queremos almacenarlos en el disco o enviarlos a un sistema diferente (por ejemplo, a través de la red). Si intenta almacenar un puntero en el disco o enviarlo a un sistema diferente, será bastante inútil porque el programa que lee ese puntero tendrá un conjunto diferente de áreas de memoria disponibles.
fuente
int seq(int i) { if (0 <= i < length) return i+1; else return -1;}
es una secuencia. Entonces, ¿cómo voy a almacenar eso en el disco?sin
en una tabla de búsqueda, que es una secuencia de números? ¿Sabía que su función es la misma que esta para las entradas que nos interesan?int seq(n) { int a[] = [1, 2, 3, 4]; return a[n]; }
¿Por qué dice exactamente que mi archivo de cuatro bytes es una representación inadecuada?Las complejidades reflejan las complejidades de los datos y los objetos mismos. Estos objetos pueden ser objetos del mundo real u objetos de computadora solamente. La respuesta está en el nombre. La serialización es la representación lineal de objetos multidimensionales. Hay muchos problemas además de la RAM fragmentada.
Si puede aplanar 12 matrices de cinco dimensiones y algún código de programa, la serialización también le permite transferir un programa de computadora completo (y datos) entre máquinas. Los protocolos informáticos distribuidos, como RMI / CORBA, utilizan la serialización ampliamente para transferir datos y programas.
Considera tu factura telefónica. Puede ser un solo objeto, que consta de todas sus llamadas (lista de cadenas), monto a pagar (entero) y país. O su factura telefónica podría estar al revés de lo anterior y consistir en llamadas telefónicas detalladas y discretas vinculadas a su nombre. Cada aplanado se verá diferente, reflejará cómo su compañía telefónica escribió esa versión de su software y la razón por la cual las bases de datos orientadas a objetos nunca despegaron.
Es posible que algunas partes de una estructura ni siquiera estén en la memoria. Si tiene un almacenamiento en caché diferido, algunas partes de un objeto solo pueden referenciarse a un archivo de disco y solo se cargan cuando se accede a esa parte de ese objeto en particular. Esto es común en los marcos de persistencia graves. Los BLOB son un buen ejemplo. Getty Images podría almacenar una enorme imagen de varios megabytes de Fidel Castro y algunos metadatos como el nombre de la imagen, el costo del alquiler y la imagen misma. Es posible que no desee cargar la imagen de 200 MB en la memoria cada vez, a menos que realmente lo mire. Serializado, todo el archivo requeriría más de 200 MB de almacenamiento.
Algunos objetos ni siquiera pueden ser serializados en absoluto. En la tierra de la programación Java, puede tener un objeto de programación que represente la pantalla de gráficos o un puerto serie físico. No hay un concepto real de serializar ninguno de ellos. ¿Cómo enviarías tu puerto a otra persona a través de una red?
Algunas cosas como contraseñas / claves de cifrado no deben almacenarse ni transmitirse. Se pueden etiquetar como tales (volátiles / transitorios, etc.) y el proceso de serialización los omitirá, pero pueden vivir en la RAM. Omitir estas etiquetas es cómo las claves de cifrado se envían / almacenan inadvertidamente en ASCII simple.
Esta y las otras respuestas es la razón por la cual es complicado.
fuente
Sí lo son. El problema aquí es el diseño de esos bytes. Un simple
int
puede tener 2, 4 u 8 bits de largo. Puede estar en endian grande o pequeño. Puede estar sin firmar, firmado con el complemento de 1 o incluso en alguna codificación de bits súper exótica como negabinary.Si simplemente descarga el
int
archivo binario de la memoria y lo llama "serializado", debe conectar prácticamente toda la computadora, el sistema operativo y su programa para que sea deserializable. O al menos, una descripción precisa de ellos.La serialización de un objeto simple consiste en escribirlo de acuerdo con algunas reglas. Esas reglas son muchas y no siempre son obvias. Por ejemplo, un
xs:integer
en XML está escrito en base-10. No es base 16, no es base 9, sino 10. No es una suposición oculta, es una regla real. Y tales reglas hacen que la serialización sea una serialización. Porque, prácticamente, no hay reglas sobre el diseño de bits de su programa en la memoria .Eso fue solo la punta de un iceberg. Tomemos un ejemplo de una secuencia de esas primitivas más simples: un C
struct
. Se podría pensar quetiene un diseño de memoria definido en una computadora determinada + SO? Pues no. Dependiendo de la
#pragma pack
configuración actual , el compilador rellenará los campos. En la configuración predeterminada de la compilación de 32 bits, ambosshorts
se rellenarán a 4 bytes, por lostruct
que en realidad tendrán 3 campos de 4 bytes en la memoria. Entonces, ahora, no solo tiene que especificar queshort
tiene 16 bits de longitud, es un número entero, escrito en complemento negativo de 1, endian grande o pequeño. También debe escribir la configuración de empaque de estructura con la que se compiló su programa.De eso se trata más o menos la serialización: hacer un conjunto de reglas y apegarse a ellas.
Esas reglas se pueden expandir para aceptar estructuras aún más sofisticadas (como listas de longitud variable o datos no lineales), características adicionales como legibilidad humana, versiones, compatibilidad con versiones anteriores y corrección de errores, etc. Pero incluso escribir una sola
int
ya es bastante complicado si usted solo quiero asegurarme de que puedas volver a leerlo de manera confiable.fuente