使用 C/C++ 有效地反序列化由浮点数、标记和空行组成的字符串

Posted

技术标签:

【中文标题】使用 C/C++ 有效地反序列化由浮点数、标记和空行组成的字符串【英文标题】:Using C/C++ to efficiently de-serialize a string comprised of floats, tokens and blank lines 【发布时间】:2010-01-14 04:57:54 【问题描述】:

我有类似以下的大字符串...

some_text_token 24.325973 -20.638823 -1.964366 0.753947 -1.290811 -3.547422 0.813014 -3.547227 0.472015 3.723311 -0.719116 3.676793 other_text_token 24.325973 20.638823 -1.964366 0.753947 -1.290811 -3.547422 -1.996611 -2.877422 0.813014 -3.547227 1.632365 2.083673 0.472015 3.723311 -0.719116 3.676793 ...

...我试图有效地从其中,并在它们出现在字符串中的交错序列中,抓取...

    文本标记 浮点值 空行

...但是我遇到了麻烦。

我尝试了 strtod 并成功地从字符串中抓取了浮点数,但我似乎无法使用 strtod 获得一个循环来向我报告交错的文本标记和空行。考虑到我也感兴趣的交错标记和空白行,我不是 100% 确信 strtod 是“正确的轨道”。

标记和空行出现在字符串中以提供浮点数的上下文,因此我的程序知道每个标记后出现的浮点值将用于什么,但 strtod 似乎更适合,可以理解的是,只报告返回浮点数它在字符串中遇到,而不考虑诸如空行或标记之类的愚蠢事物。

我知道这在概念上并不难,但是对于 C/C++ 来说相对较新,我很难判断我应该关注哪些语言特性以充分利用 C/C++ 可以带来的效率问题。

有什么建议吗?我对为什么各种方法或多或少有效地发挥作用非常感兴趣。谢谢!!!

【问题讨论】:

尝试fgets()sscanf() 【参考方案1】:

使用 C,我会做这样的事情(未经测试):

#include <stdio.h>

#define MAX 128

char buf[MAX];
while (fgets(buf, sizeof buf, fp) != NULL) 
    double d1, d2;
    if (buf[0] == '\n') 
        /* saw blank line */
     else if (sscanf(buf, "%lf%lf", &d1, &d2) != 2) 
        /* buf has the next text token, including '\n' */
     else 
        /* use the two doubles, d1, and d2 */
    

首先检查空行是因为它相对便宜。根据您的需要:

    您可能需要增加/更改MAX, 您可能需要检查buf 是否以换行符结尾,如果不是,则说明该行太长(在这种情况下转到 1 或 3), 您可能需要一个从文件中读取整行的函数,使用malloc()realloc() 动态分配缓冲区(有关更多信息,请参阅this), 您可能需要处理特殊情况,例如一行中的单个浮点值(我认为这不会发生)。 sscanf() 返回成功匹配和分配的输入项数。

我还假设空行确实是空白的(只是换行符本身)。如果没有,您将需要跳过前导空格。 isspace() in ctype.h 在这种情况下很有用。

fp 是由fopen() 返回的有效FILE * 对象。

【讨论】:

5.您可能想要检测格式错误的输入(例如“1.0 1.0foo”)。 (如果您想使用sscanf 而不是strtod,可以使用"%lf%lf%c" 作为格式字符串,并验证没有获得任何字符或者它是换行符。)【参考方案2】:

哇,我不再用 C 写很多解析器了

这已经在 OP 的输入上进行了测试

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

typedef enum 
  scan_blank, scan_label, scan_float
 tokens;

double f1, f2;

char line[512], string_token[sizeof line];

tokens scan(void) 
  char *s;
  for(s = line; *s; ++s) 
    switch(*s) 
      case ' ':
      case '\t':
        continue;
      case '\n':
        return scan_blank;
      case '0': case '1': case '2': case '3': case '4':
      case '5': case '6': case '7': case '8': case '9':
      case '.': case '-':
        sscanf(line, " %lf %lf", &f1, &f2);
        return scan_float;
      default:
        sscanf(line, " %s", string_token);
        return scan_label;
    
    abort();
  
  abort();


int main(void) 
  int n;
  for(n = 1;; ++n) 
    if (fgets(line, sizeof line, stdin) == NULL)
      return 0;
    printf("%2d %-40.*s", n, (int)strlen(line)-1, line);
    switch(scan()) 
      case scan_blank:
        printf("blank\n");
        break;
      case scan_label:
        printf("label [%s]\n", string_token);
        break;
      case scan_float:
        printf("floats [%lf %lf]\n", f1, f2);
        break;
    
  

【讨论】:

【参考方案3】:

这有点粗略且未经测试,但总体思路是尝试解析每一行并查看其中的内容:

while (!feof (stdin))

    char buf [100];
    (!fgets (buf, sizeof buf, stdin))
        break;  // end of file or error

    // skip leading whitespace
    char *cp = buf;
    while (isspace (*cp))
         ++cp;

    if (*cp == '\000')  // blank line?
    
        do_whatever_for_a_blank_line ();
        continue;
    

    // try reading a float
    double v1, v2;
    char *ep = NULL;
    v1 = strtod (cp, &ep);
    if (ep == cp)   // if nothing parsed
    
        do_whatever_for_a_text_token (cp);
        continue;
    

    while (isspace (*cp))
       ++cp;
    ep = NULL;
    v2 = strtod (cp, &ep);
    if (ep == cp)   // if no float parsed
    
         handle_single_floating_value (v1);
         continue;
    
    handle_two_floats (v1, v2);  
 

【讨论】:

cmets: fgets 返回 char *,而不是 int。大多数时候,while(!feof(fp)) ... 在 C:c-faq.com/stdio/feof.html 中是错误的。 查看您的代码更多,fgets() 返回值已修复,您没有上面链接中提到的错误。尽管如此,我还是会将fgets() 本身移动到while 的条件部分。 (我无法编辑我的最后一条评论,因此是一条新评论。) 完全正确。我已经相应地修复了fgets()。虽然feof() 有问题,但如上所示将它与fgets 结合起来效果很好。

以上是关于使用 C/C++ 有效地反序列化由浮点数、标记和空行组成的字符串的主要内容,如果未能解决你的问题,请参考以下文章

Jackson 使用自定义日期格式错误地反序列化 Joda 日期

我该如何正确地反序列化这个对象?

xsd 浮点数和空值日期

如何在 Kotlinx 序列化中序列化“任何”类型?

标记熊猫时间序列中的重复条目

Django 和空表单集是有效的