【问题标题】:How do I handle a stream of data internal to a C-based app?如何处理基于 C 的应用程序内部的数据流?
【发布时间】:2011-04-24 19:02:59
【问题描述】:

我正在从 C 应用程序中的 bzip2 流中提取数据。随着数据块从解压器中出来,它们可以被写入stdout

fwrite(buffer, 1, length, stdout);

这很好用。当它被发送到stdout时,我得到了所有的数据。

我不想写信给stdout,而是想在内部以单行块处理此语句的输出:一个以换行符结尾的字符串\n

我是否将解压缩器流的输出写入另一个缓冲区,一次一个字符,直到遇到换行符,然后调用每行处理函数?这很慢吗?有更聪明的方法吗?谢谢你的建议。

编辑

感谢您的建议。每次我通过输出缓冲区的数据时,我最终创建了一对缓冲区,将剩余部分(输出缓冲区末尾的“存根”)存储在短行缓冲区的开头。

我逐个字符地循环输出缓冲区,并一次处理换行符的数据。无换行符的剩余部分被分配和分配,并复制到下一个流的行缓冲区。似乎realloc 比重复的malloc-free 语句更便宜。

这是我想出的代码:

char bzBuf[BZBUFMAXLEN];
BZFILE *bzFp;
int bzError, bzNBuf;
char bzLineBuf[BZLINEBUFMAXLEN];
char *bzBufRemainder = NULL;
int bzBufPosition, bzLineBufPosition;

bzFp = BZ2_bzReadOpen(&bzError, *fp, 0, 0, NULL, 0); /* http://www.bzip.org/1.0.5/bzip2-manual-1.0.5.html#bzcompress-init */ 

if (bzError != BZ_OK) {
    BZ2_bzReadClose(&bzError, bzFp);   
    fprintf(stderr, "\n\t[gchr2] - Error: Bzip2 data could not be retrieved\n\n");
    return -1;          
}

bzError = BZ_OK;
bzLineBufPosition = 0;
while (bzError == BZ_OK) {

    bzNBuf = BZ2_bzRead(&bzError, bzFp, bzBuf, sizeof(bzBuf));

    if (bzError == BZ_OK || bzError == BZ_STREAM_END) {
        if (bzBufRemainder != NULL) {
            /* fprintf(stderr, "copying bzBufRemainder to bzLineBuf...\n"); */
            strncpy(bzLineBuf, bzBufRemainder, strlen(bzBufRemainder)); /* leave out \0 */
            bzLineBufPosition = strlen(bzBufRemainder);
        }

        for (bzBufPosition = 0; bzBufPosition < bzNBuf; bzBufPosition++) {
            bzLineBuf[bzLineBufPosition++] = bzBuf[bzBufPosition];
            if (bzBuf[bzBufPosition] == '\n') {
                bzLineBuf[bzLineBufPosition] = '\0'; /* terminate bzLineBuf */

                /* process the line buffer, e.g. print it out or transform it, etc. */
                fprintf(stdout, "%s", bzLineBuf);

                bzLineBufPosition = 0; /* reset line buffer position */
            }
            else if (bzBufPosition == (bzNBuf - 1)) {
                bzLineBuf[bzLineBufPosition] = '\0';
                if (bzBufRemainder != NULL)
                    bzBufRemainder = (char *)realloc(bzBufRemainder, bzLineBufPosition);
                else
                    bzBufRemainder = (char *)malloc(bzLineBufPosition);
                strncpy(bzBufRemainder, bzLineBuf, bzLineBufPosition);
            }
        }
    }
}

if (bzError != BZ_STREAM_END) {
    BZ2_bzReadClose(&bzError, bzFp);
    fprintf(stderr, "\n\t[gchr2] - Error: Bzip2 data could not be uncompressed\n\n");
    return -1;  
} else {   
    BZ2_bzReadGetUnused(&bzError, bzFp, 0, 0);
    BZ2_bzReadClose(&bzError, bzFp);
}

free(bzBufRemainder);
bzBufRemainder = NULL;

非常感谢大家的帮助。这很好用。

【问题讨论】:

  • strtok 可以让您免于检查缓冲区中每个字符的换行符。
  • strtok 是我之前尝试过的,但它对我不起作用并且我丢失了数据,因为从 bzip2 解压缩器出来的输出缓冲区被分成一行。我最后失去了那一点。也许我使用不正确。是否有使用strtok 允许保留无换行的“存根”,将该存根粘合到下一个输出缓冲区块的开头?
  • 不,你是对的,strtok 不太适合这里。如果您使用 strtok,您会丢失那一点,并且检查每个字符可能更容易。正如 Opera 所描述的那样,除了手动执行之外,我不知道有任何聪明的方法可以保存那个无换行符的“存根”以供以后使用。对不起,有点误导你:)

标签: c stream io buffer bzip2


【解决方案1】:

我认为没有更聪明的方法(除了找到已经为您完成此操作的自动机库)。为“最后一行”缓冲区分配适当的大小时要小心:如果它不能处理任意长度,并且输入来自第三方可以访问的东西,就会有安全风险。

【讨论】:

    【解决方案2】:

    我也一直在处理每行 bzip2 数据,我发现一次读取一个字节太慢了。这对我来说效果更好:

    #include <stdio.h>
    #include <stdlib.h>
    #include <bzlib.h>
    
    /* gcc -o bz bz.c -lbz2 */
    
    #define CHUNK 128
    
    struct bzdata {
      FILE *fp;
      BZFILE *bzf;
      int bzeof, bzlen, bzpos;
      char bzbuf[4096];
    };
    
    static int bz2_open(struct bzdata *bz, char *file);
    static void bz2_close(struct bzdata *bz);
    static int bz2_read_line(struct bzdata *bz, char **line, int *li);
    static int bz2_buf(struct bzdata *bz, char **line, int *li, int *ll);
    
    
    static int
    bz2_buf(struct bzdata *bz, char **line, int *li, int *ll)
    {
      int done = 0;
    
      for (; bz->bzpos < bz->bzlen && done == 0; bz->bzpos++) {
        if (*ll + 1 >= *li) {
          *li += CHUNK;
          *line = realloc(*line, (*li + 1) * sizeof(*(*line)));
        }
        if ( ((*line)[(*ll)++] = bz->bzbuf[bz->bzpos]) == '\n') {
          done = 1;
        }
      }
    
      if (bz->bzpos == bz->bzlen) {
        bz->bzpos = bz->bzlen  = 0;
      }
    
      (*line)[*ll] = '\0';
    
      return done;
    }
    
    static int
    bz2_read_line(struct bzdata *bz, char **line, int *li)
    {
      int bzerr = BZ_OK, done = 0, ll = 0;
    
      if (bz->bzpos) {
        done = bz2_buf(bz, line, li, &ll);
      }
    
      while (done == 0 && bz->bzeof == 0) {
        bz->bzlen = BZ2_bzRead(&bzerr, bz->bzf, bz->bzbuf, sizeof(bz->bzbuf));
    
        if (bzerr == BZ_OK || bzerr == BZ_STREAM_END) {
          bz->bzpos = 0;
    
          if (bzerr == BZ_STREAM_END) {
            bz->bzeof = 1;
          }
          done = bz2_buf(bz, line, li, &ll);
        } else { 
          done = -1;
        }
      }
    
      /* Handle last lines that don't have a line feed */
      if (done == 0 && ll > 0 && bz->bzeof) {
        done = 1;
      }
    
      return done;
    }
    
    static int
    bz2_open(struct bzdata *bz, char *file)
    {
      int bzerr = BZ_OK;
    
      if ( (bz->fp = fopen(file, "rb")) &&
           (bz->bzf = BZ2_bzReadOpen(&bzerr, bz->fp, 0, 0, NULL, 0)) &&
           bzerr == BZ_OK) {
        return 1;
      }
    
      return 0;
    }
    
    static void
    bz2_close(struct bzdata *bz)
    {
      int bzerr;
    
      if (bz->bzf) {
        BZ2_bzReadClose(&bzerr, bz->bzf);
        bz->bzf = NULL;
      }
    
      if (bz->fp) {
        fclose(bz->fp);
        bz->fp = NULL;
      }
      bz->bzpos = bz->bzlen = bz->bzeof = 0;
    }
    
    int main(int argc, char *argv[]) {
      struct bzdata *bz = NULL;
      int i, lc, li = 0;
      char *line = NULL;
    
      if (argc < 2) {
        return fprintf(stderr, "usage: %s file [file ...]\n", argv[0]);
      }  
    
      if ( (bz = calloc(1, sizeof(*bz))) ) {
        for (i = 1; i < argc; i++) {
          if (bz2_open(bz, argv[i])) {
            for (lc = 0; bz2_read_line(bz, &line, &li) > 0; lc++) {
              /* Process line here */
            }
            printf("%s: lines=%d\n", argv[i], lc);
          }
          bz2_close(bz);
        }
    
        free(bz);
      }
    
      if (line) {
        free(line);
      }
    
      return 0;
    }
    

    【讨论】:

      【解决方案3】:

      使用 C++ 的 std::string 很容易做到这一点,但在 C 中,如果您想有效地做到这一点,则需要一些代码(除非您使用动态字符串库)。

      char *bz_read_line(BZFILE *input)
      {
          size_t offset = 0;
          size_t len = CHUNK;  // arbitrary
          char *output = (char *)xmalloc(len);
          int bzerror;
      
          while (BZ2_bzRead(&bzerror, input, output + offset, 1) == 1) {
              if (offset+1 == len) {
                  len += CHUNK;
                  output = xrealloc(output, len);
              }
              if (output[offset] == '\n')
                  break;
              offset++;
          }
      
          if (output[offset] == '\n')
              output[offset] = '\0';  // strip trailing newline
          else if (bzerror != BZ_STREAM_END) {
              free(output);
              return NULL;
          }
      
          return output;
      }
      

      (其中xmallocxrealloc 在内部处理错误。不要忘记free 返回的字符串。)

      这几乎比bzcat慢了一个数量级:

      lars@zygmunt:/tmp$ wc foo
       1193  5841 42868 foo
      lars@zygmunt:/tmp$ bzip2 foo
      lars@zygmunt:/tmp$ time bzcat foo.bz2 > /dev/null
      
      real    0m0.010s
      user    0m0.008s
      sys     0m0.000s
      lars@zygmunt:/tmp$ time ./a.out < foo.bz2 > /dev/null
      
      real    0m0.093s
      user    0m0.044s
      sys     0m0.020s
      

      自己决定是否可以接受。

      【讨论】:

      • 我有一堆 bz2 流连接在一个非常大的文件中。我正在尝试编写一个独立的应用程序来解压缩许多流中的一个。这很有帮助,谢谢!
      【解决方案4】:

      我认为您应该将字符块复制到另一个缓冲区,直到您写入的最新块包含换行符为止。然后你就可以在整条线上工作了。

      您可以将缓冲区的其余部分('\n' 之后)保存到临时文件中,然后从中创建一个新行。

      【讨论】:

        猜你喜欢
        • 2018-01-28
        • 1970-01-01
        • 2012-07-16
        • 1970-01-01
        • 2019-06-23
        • 1970-01-01
        • 2011-11-10
        • 2011-09-07
        • 1970-01-01
        相关资源
        最近更新 更多