C 将大文件读入 char* 数组太慢
C read big file into char* array too slow
我想读取一个大文件,而一行的第一个字符不是“ ”。
但是我写的代码很慢。我怎样才能加快例程?
有没有比getline更好的解决方案?
void readString(const char *fn)
{
FILE *fp;
char *vString;
struct stat fdstat;
int stat_res;
stat_res = stat(fn, &fdstat);
fp = fopen(fn, "r+b");
if (fp && !stat_res)
{
vString = (char *)calloc(fdstat.st_size + 1, sizeof(char));
int dataEnd = 1;
size_t len = 0;
int emptyLine = 1;
char **linePtr = malloc(sizeof(char*));
*linePtr = NULL;
while(dataEnd)
{
// Check every line
getline(linePtr, &len, fp);
// When data ends, the line begins with space (" ")
if(*linePtr[0] == 0x20)
emptyLine = 0;
// If line begins with space, stop writing
if(emptyLine)
strcat(vString, *linePtr);
else
dataEnd = 0;
}
strcat(vString, "[=10=]");
free(linePtr);
linePtr = NULL;
}
}
int main(int argc, char **argv){
readString(argv[1]);
return EXIT_SUCCESS;
}
您是否尝试过使用fread读取文件并在每一步中读取更大的数据块,然后在读取后解析数据?类似于:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
char *readString(const char *fn)
{
FILE *fp;
char *vString;
struct stat fdstat;
int stat_res;
stat_res = stat(fn, &fdstat);
fp = fopen(fn, "r+b");
if (fp && !stat_res) {
vString = (char *) calloc(fdstat.st_size + 1, sizeof(char));
int newline = 1;
int index = 0;
while (index < fdstat.st_size) {
int len =
fdstat.st_size - index >
4096 ? 4096 : fdstat.st_size - index;
char *buffer = (char *) malloc(len);
int read_len = fread(buffer, 1, len, fp);
int i;
if (newline) {
if (read_len > 0 && buffer[0] == ' ') {
return vString;
}
newline = 0;
}
for (i = 0; i < read_len; ++i) {
if (buffer[i] == '\n') {
if (i + 1 < read_len && buffer[i + 1] == ' ') {
memcpy(vString + index, buffer, i + 1);
return vString;
}
newline = 1;
}
}
memcpy(vString + index, buffer, read_len);
index += read_len;
}
}
return vString;
}
int main(int argc, char **argv)
{
char *str = readString(argv[1]);
printf("%s", str);
free(str);
return EXIT_SUCCESS;
}
How can I speed up the routine?
您的程序性能方面最可疑的方面是 strcat()
。在每次调用时,它需要从头开始扫描整个目标字符串,以找到追加源字符串的位置。因此,如果您的文件行的长度受常数限制(即使是一个很大的常数),那么您的方法的性能与文件长度的平方成正比。
不过,渐近复杂性分析不一定能说明全部情况。代码的 I/O 部分随文件长度线性扩展,并且由于 I/O 比内存中数据操作昂贵得多,因此对于足够小的文件,这将主导您的性能。如果你处于那种状态,那么你可能不会比现在做得更好。不过,在那种情况下,您仍然可以通过 fread()
一次读取整个文件,然后通过 strstr()
:
扫描它以查找数据结尾,从而做得更好。
size_t nread = fread(vString, 1, fdstat.st_size, fp);
// Handle nread != fdstat.st_size ...
// terminate the buffer as a string
vString[nread] = '[=10=]';
// truncate the string after the end-of-data:
char *eod = strstr(vString, "\n ");
if (eod) {
// terminator found - truncate the string after the newline
eod[1] = '[=10=]';
} // else no terminator found
它是线性扩展的,所以它也解决了你的渐近复杂性问题,但如果感兴趣的数据通常比文件短得多,那么在那些情况下它会让你做更多的事情 I/O 比你需要做的。在这种情况下,一种替代方法是按照@laissez_faire 的建议,分块读取。另一种方法是调整您的原始算法以跟踪 vString
的末尾,以便使用 strcpy()
而不是 strcat()
来附加每个新行。该版本的关键部分如下所示:
char *linePtr = NULL;
size_t nread = 0;
size_t len = 0;
*vString = '[=11=]'; // In case the first line is end-of-data
for (char *end = vString; ; end += nread) {
// Check every line
nread = getline(&linePtr, &len, fp);
if (nread < 0) {
// handle eof or error ...
}
// When data ends, the line begins with space (" ")
if (*linePtr == ' ') {
break;
}
strcpy(end, *linePtr);
}
free(linePtr);
此外,请注意
您不需要最初对分配给 *vString
的内存进行零填充,因为您只是要用真正感兴趣的数据覆盖这些零(然后忽略缓冲区的其余部分)。
您不应强制转换 malloc()
系列函数的 return 值,包括 calloc()
.
我想读取一个大文件,而一行的第一个字符不是“ ”。 但是我写的代码很慢。我怎样才能加快例程? 有没有比getline更好的解决方案?
void readString(const char *fn)
{
FILE *fp;
char *vString;
struct stat fdstat;
int stat_res;
stat_res = stat(fn, &fdstat);
fp = fopen(fn, "r+b");
if (fp && !stat_res)
{
vString = (char *)calloc(fdstat.st_size + 1, sizeof(char));
int dataEnd = 1;
size_t len = 0;
int emptyLine = 1;
char **linePtr = malloc(sizeof(char*));
*linePtr = NULL;
while(dataEnd)
{
// Check every line
getline(linePtr, &len, fp);
// When data ends, the line begins with space (" ")
if(*linePtr[0] == 0x20)
emptyLine = 0;
// If line begins with space, stop writing
if(emptyLine)
strcat(vString, *linePtr);
else
dataEnd = 0;
}
strcat(vString, "[=10=]");
free(linePtr);
linePtr = NULL;
}
}
int main(int argc, char **argv){
readString(argv[1]);
return EXIT_SUCCESS;
}
您是否尝试过使用fread读取文件并在每一步中读取更大的数据块,然后在读取后解析数据?类似于:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
char *readString(const char *fn)
{
FILE *fp;
char *vString;
struct stat fdstat;
int stat_res;
stat_res = stat(fn, &fdstat);
fp = fopen(fn, "r+b");
if (fp && !stat_res) {
vString = (char *) calloc(fdstat.st_size + 1, sizeof(char));
int newline = 1;
int index = 0;
while (index < fdstat.st_size) {
int len =
fdstat.st_size - index >
4096 ? 4096 : fdstat.st_size - index;
char *buffer = (char *) malloc(len);
int read_len = fread(buffer, 1, len, fp);
int i;
if (newline) {
if (read_len > 0 && buffer[0] == ' ') {
return vString;
}
newline = 0;
}
for (i = 0; i < read_len; ++i) {
if (buffer[i] == '\n') {
if (i + 1 < read_len && buffer[i + 1] == ' ') {
memcpy(vString + index, buffer, i + 1);
return vString;
}
newline = 1;
}
}
memcpy(vString + index, buffer, read_len);
index += read_len;
}
}
return vString;
}
int main(int argc, char **argv)
{
char *str = readString(argv[1]);
printf("%s", str);
free(str);
return EXIT_SUCCESS;
}
How can I speed up the routine?
您的程序性能方面最可疑的方面是 strcat()
。在每次调用时,它需要从头开始扫描整个目标字符串,以找到追加源字符串的位置。因此,如果您的文件行的长度受常数限制(即使是一个很大的常数),那么您的方法的性能与文件长度的平方成正比。
不过,渐近复杂性分析不一定能说明全部情况。代码的 I/O 部分随文件长度线性扩展,并且由于 I/O 比内存中数据操作昂贵得多,因此对于足够小的文件,这将主导您的性能。如果你处于那种状态,那么你可能不会比现在做得更好。不过,在那种情况下,您仍然可以通过 fread()
一次读取整个文件,然后通过 strstr()
:
size_t nread = fread(vString, 1, fdstat.st_size, fp);
// Handle nread != fdstat.st_size ...
// terminate the buffer as a string
vString[nread] = '[=10=]';
// truncate the string after the end-of-data:
char *eod = strstr(vString, "\n ");
if (eod) {
// terminator found - truncate the string after the newline
eod[1] = '[=10=]';
} // else no terminator found
它是线性扩展的,所以它也解决了你的渐近复杂性问题,但如果感兴趣的数据通常比文件短得多,那么在那些情况下它会让你做更多的事情 I/O 比你需要做的。在这种情况下,一种替代方法是按照@laissez_faire 的建议,分块读取。另一种方法是调整您的原始算法以跟踪 vString
的末尾,以便使用 strcpy()
而不是 strcat()
来附加每个新行。该版本的关键部分如下所示:
char *linePtr = NULL;
size_t nread = 0;
size_t len = 0;
*vString = '[=11=]'; // In case the first line is end-of-data
for (char *end = vString; ; end += nread) {
// Check every line
nread = getline(&linePtr, &len, fp);
if (nread < 0) {
// handle eof or error ...
}
// When data ends, the line begins with space (" ")
if (*linePtr == ' ') {
break;
}
strcpy(end, *linePtr);
}
free(linePtr);
此外,请注意
您不需要最初对分配给
*vString
的内存进行零填充,因为您只是要用真正感兴趣的数据覆盖这些零(然后忽略缓冲区的其余部分)。您不应强制转换
malloc()
系列函数的 return 值,包括calloc()
.