【问题标题】:Sorting Records from a binary file in C从 C 中的二进制文件中对记录进行排序
【发布时间】:2017-02-02 14:04:15
【问题描述】:

我正在开发一个程序,它使用 read()、write()、open() 和 close() 来处理文件。我们得到一个二进制文件来排序。

我的困惑始于阅读步骤。据我了解, read 将文件内容放入字符数组中。因此,如果我没有完全关闭,这意味着每个索引都包含一个信息字节。每条记录用空格隔开。我将按每个包含的前四个字节对它们进行排序。

我知道记录的格式,但数据的范围是可变的。幸运的是,记录之间只有空格,没有一个空格。该结构是一个整数,作为文件头,表示有多少条记录。每个键是 4 个字节,后跟 4 个字节表示有多少数据,然后是所有数据,没有空格。数据大小不包括空格。

C 库中的排序例程是否可以将这些作为字符而不是整数处理?另外,我不确定从哪里开始分离和重新排列记录。我是否必须将每个提取到一个记录结构数组中并从那里排序?

我是 C 新手,使用这些特定功能在网上找不到太多内容。它来自家庭作业,但截止日期已过;我只是想加快我的理解。

【问题讨论】:

  • 你知道二进制记录有多长吗?记录中间可以有空格,还是空格只出现在记录的末尾?你知道二进制数据的结构吗? (如果你不这样做,你就无法在有用的排序方式上做很多事情。)
  • 我知道记录的格式,但数据的范围是可变的。幸运的是,记录之间只有空格,没有一个空格。该结构是一个整数,作为文件头,表示有多少条记录。每个key为4个字节,后面4个字节表示有多少数据,后面的数据全部不带空格。
  • 以后,请在问题中添加额外信息——您可以编辑自己的问题。这次我已经为你做了。长度包括空格吗?大概不会。原则上,假设您有键 + 长度 + 数据,数据中可能会有空格并且没有问题,因为您知道记录的末尾在哪里。您是否打算按关键顺序对记录进行排序?密钥是原生格式 32 位 int,还是其他一些特殊格式(固定的大端、固定的小端,等等)?同样,长度的格式是什么?
  • 你熟悉结构吗?您是否了解过灵活的数组成员 (struct tag { …; SomeType array[]; };)?他们非常适合这个:struct record { int key; int length; unsigned char data[]; };。你真的应该展示你所拥有的代码——它可以帮助我们以适当的复杂程度来回答。但是数据格式设计得非常简洁,这很有帮助。
  • 谢谢乔纳森·莱弗勒!我不想用文字墙让人们陷入困境,下次我会添加更多。如果有帮助的话,我也可以添加我的代码,但是我在文件上没有任何过去的 read() 内容,因为我一直不知道如何处理它。老实说,我不知道密钥是否是本机格式,他们在作业中说它是 4 个字节,这就是我们得到的所有信息。长度也是 4 个字节。

标签: c arrays sorting file-io


【解决方案1】:

如果文件是二进制文件,在您编写时 - 那么记录不会被任何东西分隔,您只需要知道每条记录的大小(所有记录可能具有相同的大小)。

对于排序,可以使用qsort等标准库函数。此函数使用您提供的回调,因此它可以处理任何类型的数据。 qsort 返回后,您将重新排列数据。

我是否必须将每个都提取到一个记录结构数组中并从那里排序?

是的,对于少量记录(如学生作业),这是一个不错的选择。

【讨论】:

  • 这很有趣,我不知道。记录不是固定大小的。我们用来排序的键是,但关联的数据可以在一个范围内。感谢您提供有关 qsort 的信息,很高兴知道在这种情况下我可以在不进行类型转换的情况下使用它。我被允许使用 qsort。很高兴知道我在制作结构数组方面走在了正确的轨道上。为了做到这一点,我可以以某种方式寻找一个空间来象征每条记录的结束吗?我有点不确定如何将它们放入一个具有整个可变数据大小的数组中并迭代原始缓冲区数组本身。
  • > 记录不是固定大小的 然后你可以这样做:分配一个指向记录的指针数组,并对指针进行排序。显然,所有指针都具有相同的大小。缺点是分配额外的内存。
  • 我没想到!谢谢你。我仍在与自己一起制定指针。然后我可以在排序中使用指针指向的前四个字节吗?我还有一个关于分配额外内存是什么意思的问题。你的意思是我必须使用 malloc 或类似的东西吗?我仍然非常不确定何时使用 malloc 是必要的还是合适的。
  • 是的,malloc。指针数组与结构数组之间的区别将是 qsort 回调中的地址
【解决方案2】:

测试数据生成

我们没有任何示例数据,所以我们必须创建一些。让我们使用纯文本行作为“数据”部分,我们可以生成 0..999 范围内的随机键,并将行的长度报告为数据的长度,并在每个末尾包含空白垫记录。

例如,此代码执行生成工作,从标准输入读取并写入标准输出:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

int main(void)
{
    srand(time(0));

    int fd = STDOUT_FILENO;
    char *buffer = 0;
    size_t buflen = 0;
    int len;
    while ((len = getline(&buffer, &buflen, stdin)) != -1)
    {
        int key = rand() % 1000;
        write(fd, &key, sizeof(key));
        write(fd, &len, sizeof(len));
        write(fd, buffer, len);
        write(fd, " ", 1);
    }
    free(buffer);
    return 0;
}

鉴于输入文件(搜索“伟大的 panjandrum”以找出文本的来源——这并不是很明智):

So she went into the garden
to cut a cabbage-leaf
to make an apple-pie
and at the same time
a great she-bear coming down the street
pops its head into the shop
What no soap
So he died
and she very imprudently married the Barber
and there were present
the Picninnies
and the Joblillies
and the Garyulies
and the great Panjandrum himself
with the little round button at top
and they all fell to playing the game of catch-as-catch-can
till the gunpowder ran out at the heels of their boots

输出可能是:

0x0000: C3 03 00 00 1C 00 00 00 53 6F 20 73 68 65 20 77   ........So she w
0x0010: 65 6E 74 20 69 6E 74 6F 20 74 68 65 20 67 61 72   ent into the gar
0x0020: 64 65 6E 0A 20 C7 01 00 00 16 00 00 00 74 6F 20   den. ........to 
0x0030: 63 75 74 20 61 20 63 61 62 62 61 67 65 2D 6C 65   cut a cabbage-le
0x0040: 61 66 0A 20 6C 03 00 00 15 00 00 00 74 6F 20 6D   af. l.......to m
0x0050: 61 6B 65 20 61 6E 20 61 70 70 6C 65 2D 70 69 65   ake an apple-pie
0x0060: 0A 20 6F 02 00 00 15 00 00 00 61 6E 64 20 61 74   . o.......and at
0x0070: 20 74 68 65 20 73 61 6D 65 20 74 69 6D 65 0A 20    the same time. 
0x0080: 80 02 00 00 28 00 00 00 61 20 67 72 65 61 74 20   ....(...a great 
0x0090: 73 68 65 2D 62 65 61 72 20 63 6F 6D 69 6E 67 20   she-bear coming 
0x00A0: 64 6F 77 6E 20 74 68 65 20 73 74 72 65 65 74 0A   down the street.
0x00B0: 20 F5 02 00 00 1C 00 00 00 70 6F 70 73 20 69 74    ........pops it
0x00C0: 73 20 68 65 61 64 20 69 6E 74 6F 20 74 68 65 20   s head into the 
0x00D0: 73 68 6F 70 0A 20 10 01 00 00 0D 00 00 00 57 68   shop. ........Wh
0x00E0: 61 74 20 6E 6F 20 73 6F 61 70 0A 20 4F 02 00 00   at no soap. O...
0x00F0: 0B 00 00 00 53 6F 20 68 65 20 64 69 65 64 0A 20   ....So he died. 
0x0100: 73 01 00 00 2C 00 00 00 61 6E 64 20 73 68 65 20   s...,...and she 
0x0110: 76 65 72 79 20 69 6D 70 72 75 64 65 6E 74 6C 79   very imprudently
0x0120: 20 6D 61 72 72 69 65 64 20 74 68 65 20 42 61 72    married the Bar
0x0130: 62 65 72 0A 20 60 01 00 00 17 00 00 00 61 6E 64   ber. `.......and
0x0140: 20 74 68 65 72 65 20 77 65 72 65 20 70 72 65 73    there were pres
0x0150: 65 6E 74 0A 20 0D 00 00 00 0F 00 00 00 74 68 65   ent. ........the
0x0160: 20 50 69 63 6E 69 6E 6E 69 65 73 0A 20 46 02 00    Picninnies. F..
0x0170: 00 13 00 00 00 61 6E 64 20 74 68 65 20 4A 6F 62   .....and the Job
0x0180: 6C 69 6C 6C 69 65 73 0A 20 88 02 00 00 12 00 00   lillies. .......
0x0190: 00 61 6E 64 20 74 68 65 20 47 61 72 79 75 6C 69   .and the Garyuli
0x01A0: 65 73 0A 20 92 00 00 00 21 00 00 00 61 6E 64 20   es. ....!...and 
0x01B0: 74 68 65 20 67 72 65 61 74 20 50 61 6E 6A 61 6E   the great Panjan
0x01C0: 64 72 75 6D 20 68 69 6D 73 65 6C 66 0A 20 A8 01   drum himself. ..
0x01D0: 00 00 24 00 00 00 77 69 74 68 20 74 68 65 20 6C   ..$...with the l
0x01E0: 69 74 74 6C 65 20 72 6F 75 6E 64 20 62 75 74 74   ittle round butt
0x01F0: 6F 6E 20 61 74 20 74 6F 70 0A 20 15 01 00 00 3C   on at top. ....<
0x0200: 00 00 00 61 6E 64 20 74 68 65 79 20 61 6C 6C 20   ...and they all 
0x0210: 66 65 6C 6C 20 74 6F 20 70 6C 61 79 69 6E 67 20   fell to playing 
0x0220: 74 68 65 20 67 61 6D 65 20 6F 66 20 63 61 74 63   the game of catc
0x0230: 68 2D 61 73 2D 63 61 74 63 68 2D 63 61 6E 0A 20   h-as-catch-can. 
0x0240: B1 03 00 00 37 00 00 00 74 69 6C 6C 20 74 68 65   ....7...till the
0x0250: 20 67 75 6E 70 6F 77 64 65 72 20 72 61 6E 20 6F    gunpowder ran o
0x0260: 75 74 20 61 74 20 74 68 65 20 68 65 65 6C 73 20   ut at the heels 
0x0270: 6F 66 20 74 68 65 69 72 20 62 6F 6F 74 73 0A 20   of their boots. 
0x0280:

对生成的数据进行排序

现在我们有了可由程序处理以读取、打印、排序和打印数据的数据。

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

struct record
{
    int   key;
    int   data_len;
    char  data[];
};

static int comparator(const void *v1, const void *v2);
static void print_records(const char *tag, int num_recs, struct record **recs);
static void err_syserr(const char *msg, ...);
static void err_setarg0(const char *argv0);

int main(int argc, char **argv)
{
    if (argc != 2)
    {
        fprintf(stderr, "Usage: %s file\n", argv[0]);
        return 1;
    }

    err_setarg0(argv[0]);

    int fd = open(argv[1], O_RDONLY);
    if (fd < 0)
        err_syserr("Failed to open file '%s' for reading\n", argv[1]);

    struct record **records = 0;
    int num_recs = 0;
    int max_recs = 0;
    int key;
    int len;
    while (read(fd, &key, sizeof(key)) == sizeof(key) &&
           read(fd, &len, sizeof(len)) == sizeof(len))
    {
        //printf("rec num %d (key %d, len %d)\n", num_recs, key, len);
        assert(len > 0);
        assert(num_recs <= max_recs);
        if (num_recs == max_recs)
        {
            size_t new_max = 2 * max_recs + 2;
            void *new_recs = realloc(records, new_max * sizeof(*records));
            if (new_recs == 0)
                err_syserr("Failed to realloc() %zu bytes of memory\n", new_max * sizeof(*records));
            records = new_recs;
            max_recs = new_max;
        }
        int rec_size = sizeof(struct record) + len;
        records[num_recs] = malloc(rec_size);
        records[num_recs]->key = key;
        records[num_recs]->data_len = len;
        if (read(fd, records[num_recs]->data, len) != len)
            err_syserr("Short read for record number %d (key %d)\n", num_recs, key);
        records[num_recs]->data[len-1] = '\0';
        //printf("Data: [%s]\n", records[num_recs]->data);
        char blank = 0;
        if (read(fd, &blank, sizeof(blank)) != sizeof(blank))
            err_syserr("Missing record terminator after record number %d (key %d)\n", num_recs, key);
        if (blank != ' ')
            err_syserr("Unexpected EOR code %d for record number %d (key %d)\n", blank, num_recs, key);
        num_recs++;
    }
    close(fd);

    print_records("Before", num_recs, records);
    qsort(records, num_recs, sizeof(struct record *), comparator);
    print_records("After", num_recs, records);

    for (int i = 0; i < num_recs; i++)
        free(records[i]);
    free(records);

    return 0;
}

static int comparator(const void *v1, const void *v2)
{
    int key_1 = (*(struct record **)v1)->key;
    int key_2 = (*(struct record **)v2)->key;
    if (key_1 < key_2)
        return -1;
    else if (key_1 > key_2)
        return +1;
    else
        return 0;
}

static void print_records(const char *tag, int num_recs, struct record **recs)
{
    printf("%s (%d records):\n", tag, num_recs);
    for (int i = 0; i < num_recs; i++)
    {
        struct record *rec = recs[i];
        printf("%6d: %4d: %s\n", rec->key, rec->data_len, rec->data);
    }
}

/* My standard error handling - stderr.h and stderr.c */
static const char *arg0 = "unknown";

static void err_setarg0(const char *argv0)
{
    arg0 = argv0;
}

static void err_syserr(const char *fmt, ...)
{
    va_list args;
    int errnum = errno;
    fprintf(stderr, "%s: ", arg0);
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
    if (errnum != 0)
        fprintf(stderr, "(%d: %s)\n", errnum, strerror(errnum));
    exit(EXIT_FAILURE);
}

代码利用每条记录的数据以换行符结尾的知识,并用空字节覆盖该换行符。这也使演示文稿更好。另外,请注意,您不能创建具有灵活数组成员的结构数组(因为数组的元素大小都相同,而具有灵活数组成员的结构的大小也不完全相同)。因此,代码使用指向具有灵活数组成员的结构的指针数组。这会影响比较器功能等。

部分由于数据格式比较复杂,代码会小心识别错误(格式错误的)数据。

样品运行

请注意,输出中的第一列是记录键——目标是将数据按键号的升序排序。第二列是数据长度。

Before (17 records):
   963:   28: So she went into the garden
   455:   22: to cut a cabbage-leaf
   876:   21: to make an apple-pie
   623:   21: and at the same time
   640:   40: a great she-bear coming down the street
   757:   28: pops its head into the shop
   272:   13: What no soap
   591:   11: So he died
   371:   44: and she very imprudently married the Barber
   352:   23: and there were present
    13:   15: the Picninnies
   582:   19: and the Joblillies
   648:   18: and the Garyulies
   146:   33: and the great Panjandrum himself
   424:   36: with the little round button at top
   277:   60: and they all fell to playing the game of catch-as-catch-can
   945:   55: till the gunpowder ran out at the heels of their boots
After (17 records):
    13:   15: the Picninnies
   146:   33: and the great Panjandrum himself
   272:   13: What no soap
   277:   60: and they all fell to playing the game of catch-as-catch-can
   352:   23: and there were present
   371:   44: and she very imprudently married the Barber
   424:   36: with the little round button at top
   455:   22: to cut a cabbage-leaf
   582:   19: and the Joblillies
   591:   11: So he died
   623:   21: and at the same time
   640:   40: a great she-bear coming down the street
   648:   18: and the Garyulies
   757:   28: pops its head into the shop
   876:   21: to make an apple-pie
   945:   55: till the gunpowder ran out at the heels of their boots
   963:   28: So she went into the garden

【讨论】:

  • 谢谢,我发现代码示例的详细解释很有帮助。
猜你喜欢
  • 1970-01-01
  • 2018-04-29
  • 1970-01-01
  • 2022-01-18
  • 2021-12-16
  • 2011-11-28
  • 2019-02-04
  • 1970-01-01
  • 2020-07-30
相关资源
最近更新 更多