Простая сериализация данных в C
В настоящее время я занимаюсь редизайном приложения и столкнулся с проблемой сериализации некоторых данных.
Скажем, у меня есть массив размером mxn
double **data;
что я хочу сериализовать в
char *dataSerialized
используя простые разделители (один для строк, один для элементов).
Десериализация довольно проста: подсчет разделителей и распределение размера данных, которые нужно сохранить. Однако, что касается функции сериализации, скажем,
serialize_matrix(double **data, int m, int n, char **dataSerialized);
Какова была бы лучшая стратегия, чтобы определить размер, необходимый массиву char, и выделить для него соответствующую память?
Возможно, с использованием некоторого экспоненциального представления фиксированной ширины значений типа double в строке? Можно ли просто преобразовать все байты double в char и иметь размерный (double) выровненный массив char? Как бы я сохранил точность чисел?
НОТА:
Мне нужны данные в массиве символов, а не в двоичном, не в файле.
Сериализованные данные будут отправлены по сети с помощью ZeroMQ между сервером C и клиентом Java. Возможно ли, учитывая размеры и размер массива (double), что его всегда можно точно восстановить между этими двумя?
2 ответа
Java имеет довольно хорошую поддержку для чтения необработанных байтов и преобразования во что угодно. Вы можете выбрать простой проводной формат, а затем сериализовать его в C и отменить сериализацию в Java.
Вот пример чрезвычайно простого формата с кодом для десериализации и сериализации.
Я написал немного большую тестовую программу, которую я могу выложить куда-нибудь, если хотите; он создает случайный массив данных в C, сериализует, записывает сериализованную строку в кодировке base64 в стандартный вывод. Затем гораздо меньшая Java-программа читает, декодирует и десериализует это.
Код C для сериализации:
/*
I'm using this format:
32 bit signed int 32 bit signed int See below
[number of elements in outer array] [number of elements in inner array] [elements]
[elements] is buildt like
[element(0,0)][element(0,1)]...[element(0,y)][element(1,0)]...
each element is sendt like a 64 bit iee754 "double". If your C compiler/architecture is doing something different with its "double"'s, look forward to hours of fun :)
I'm using a couple non-standard functions for byte-swapping here, originally from a BSD, but present in glibc>=2.9.
*/
/* Calculate the bytes required to store a message of x*y doubles */
size_t calculate_size(size_t x, size_t y)
{
/* The two dimensions in the array - each in 32 bits - (2 * 4)*/
size_t sz = 8;
/* a 64 bit IEE754 is by definition 8 bytes long :) */
sz += ((x * y) * 8);
/* and a NUL */
sz++;
return sz;
}
/* Helpers */
static char* write_int32(int32_t, char*);
static char* write_double(double, char*);
/* Actual conversion. That wasn't so hard, was it? */
void convert_data(double** src, size_t x, size_t y, char* dst)
{
dst = write_int32((int32_t) x, dst);
dst = write_int32((int32_t) y, dst);
for(int i = 0; i < x; i++) {
for(int j = 0; j < y; j++) {
dst = write_double(src[i][j], dst);
}
}
*dst = '\0';
}
static char* write_int32(int32_t num, char* c)
{
char* byte;
int i = sizeof(int32_t);
/* Convert to network byte order */
num = htobe32(num);
byte = (char*) (&num);
while(i--) {
*c++ = *byte++;
}
return c;
}
static char* write_double(double d, char* c)
{
/* Here I'm assuming your C programs use IEE754 'double' precision natively.
If you don't, you should be able to convert into this format. A helper library most likely already exists for your platform.
Note that IEE754 endianess isn't defined, but in practice, normal platforms use the same byte order as they do for integers.
*/
char* byte;
int i = sizeof(uint64_t);
uint64_t num = *((uint64_t*)&d);
/* convert to network byte order */
num = htobe64(num);
byte = (char*) (&num);
while(i--) {
*c++ = *byte++;
}
return c;
}
Java-код для десериализации:
/* The raw char array from c is now read into the byte[] `bytes` in java */
DataInputStream stream = new DataInputStream(new ByteArrayInputStream(bytes));
int dim_x; int dim_y;
double[][] data;
try {
dim_x = stream.readInt();
dim_y = stream.readInt();
data = new double[dim_x][dim_y];
for(int i = 0; i < dim_x; ++i) {
for(int j = 0; j < dim_y; ++j) {
data[i][j] = stream.readDouble();
}
}
System.out.println("Client:");
System.out.println("Dimensions: "+dim_x+" x "+dim_y);
System.out.println("Data:");
for(int i = 0; i < dim_x; ++i) {
for(int j = 0; j < dim_y; ++j) {
System.out.print(" "+data[i][j]);
}
System.out.println();
}
} catch(IOException e) {
System.err.println("Error reading input");
System.err.println(e.getMessage());
System.exit(1);
}
Если вы пишете двоичный файл, вам следует подумать о хорошем способе сериализации реальных двоичных данных (64-битных) вашего double
, Это может перейти от прямой записи содержимого двойника к файлу (с учетом порядка байтов) к некоторым более сложным нормализующим схемам сериализации (например, с четко определенным представлением NaN). Это зависит от вас на самом деле. Если вы ожидаете, что вы будете в основном среди однородных архитектур, прямого дампа памяти, вероятно, будет достаточно.
Если вы хотите записать в текстовый файл и ищете ASCII-представление, я бы настоятельно не рекомендовал десятичное числовое представление. Вместо этого вы можете преобразовать 64-битные необработанные данные в ASCII, используя base64 или что-то в этом роде.
Вы действительно хотите сохранить всю точность, которую вы имеете в своем double
!