我认为这是一个非常普遍的问题。让我举个例子。
我有一个文件,其中包含许多行(例如一百万行),每行的格式如下:首先是一个数字X
,然后是一个长度为{{1}的字符串}。
现在我想读取文件并存储所有字符串(无论出于何种原因)。通常,我要做的是:对于每一行我读取长度X
,并使用X
(在C中)或malloc
(在C ++中)来分配new
字节,然后读取字符串。
我不喜欢这种方法的原因:可能会发生大多数字符串非常短,比如8字节以下。在这种情况下,根据我的理解,分配在时间和空间上都会非常浪费。
(第一个问题:我理解正确,分配小块内存是浪费吗?)
我考虑过以下优化:每次我分配一个大块,比如1024字节,每当需要一小块时,只需从大块中剪切它。这种方法的问题在于,解除分配变得几乎不可能......
听起来好像我想自己做内存管理......但是,我想知道是否有更好的方法?如果需要,我不介意使用一些数据结构来进行管理。
如果你有一些好主意只能有条件地工作(例如,知道大多数作品都很小),我也很乐意知道。
答案 0 :(得分:1)
是的,静态分配大容量缓冲区并读取这是读取数据的常用方法。
假设您选择1KB作为缓冲区大小,因为您希望大多数读取都适合。
您是否能够将超过1KB的罕见读数切成多个读数?
或不?
static const unsigned int BUF_SIZE = 1024;
static char buf[BUF_SIZE];
while (something) {
const unsigned int num_bytes_to_read = foo();
const char* data = 0;
if (num_bytes_to_read <= BUF_SIZE) {
read_into(&buf[0]);
data = buf;
}
else {
data = new char[num_bytes_to_read];
read_into(data);
}
// use data
if (num_bytes_to_read > BUF_SIZE)
delete[] data;
}
此代码是C,C ++和伪代码的令人愉快的混搭,因为您没有指定语言。
如果您实际使用的是C ++,那么只需使用向量即可。清酒;如果需要,让它增长,但只是重新使用它的存储。
答案 1 :(得分:1)
&#34;自然&#34;进行内存分配的方法是确保每个内存块至少足以包含一个指针和一个大小,或者一些类似的簿记,足以维护一个空闲节点的结构。细节有所不同,但您可以通过查看在进行小分配时从分配器返回的实际地址来实验性地观察开销。
这就是小分配的意义所在&#34; wasty&#34;。实际上,对于大多数C或C ++实现,所有块都被四舍五入为2的幂的倍数(功率取决于分配器,有时取决于分配的数量级)。因此,所有分配都是令人讨厌的,但是如果将大量的1字节和2字节分配填充到16字节,那么比例说来更浪费,而不是将大量的113和114字节分配填充到128字节。 / p>
如果您愿意放弃释放和重复使用单一分配的能力(例如,如果您在计划完全解除后,计划将所有内容全部释放,这很好关于这个文件的内容)然后肯定,你可以以更紧凑的方式分配许多小字符串。例如,将它们全部放在一个或几个大的分配中,每个字符串都以nul结尾,并处理指向每个字符串的第一个字节的指针。每个字符串的开销是1或0字节,具体取决于您如何考虑nul。如果你只是用nul字节覆盖换行符,那么在将文件拆分成行的情况下,这可以特别巧妙地工作。显然,您不必介意从每一行删除换行符!
如果您需要释放和重复使用,并且您知道所有分配大小相同,那么您可以取消簿记的大小,并编写自己的分配器(或者,在实践中,找到您满意的现有池分配器)。最小分配大小可以是一个指针。但是,如果所有字符串都低于指针大小,那么这只是一个轻松的胜利,&#34;大多数&#34;并非如此直截了当。
答案 2 :(得分:0)
您可以先计算文本行数及其总长度,然后分配一块内存来存储文本,并分配一个块来存储指针。通过第二次读取文件来填充这些块。请记住添加终止零。
答案 3 :(得分:0)
如果整个文件适合内存,那么为什么不获取文件的大小,分配那么多的内存和足够的指针,然后读入整个文件并创建一个指向文件中行的指针数组?
答案 4 :(得分:0)
我会存储&#34; x&#34;使用最大的缓冲区我可以。 您没有告诉我们x的最大尺寸是sizeof(x)。我认为将它存储在缓冲区以避免每个单词的寻址并相对快速地访问它是至关重要的。
类似的东西:
char *buffer = "word1\0word2\0word3\0";
同时放置地址或......等等。快速&#39;访问
就像这样:
char *buffer = "xx1word1xx2word2xx3word3\0\0\0\0";
正如你所看到的那样,在一个固定大小的x上,无需存储每个地址就可以真正有效地跳转到单词,只需要读取x并使用x来跳转地增加addr ... x不是用char转换的,整数注入和读取使用他的类型大小,不需要字符串\ 0的结尾这样的单词,只有完整的buff知道缓冲区的结束(如果x == 0那么它的结束)。
我对解释感谢我的英语并不是很好,我推荐一些代码作为更好的解释:
#include <stdio.h>
#include <stdint.h>
#include <string.h>
void printword(char *buff){
char *ptr;
int i;
union{
uint16_t x;
char c[sizeof(uint16_t)];
}u;
ptr=buff;
memcpy(u.c,ptr,sizeof(uint16_t));
while(u.x){
ptr+=sizeof(u.x);
for(i=0;i<u.x;i++)printf("%c",buff[i+(ptr-buff)]);/*jump in buff using x*/
printf("\n");
ptr+=u.x;
memcpy(u.c,ptr,sizeof(uint16_t));
}
}
void addword(char *buff,const char *word,uint16_t x){
char *ptr;
union{
uint16_t x;
char c[sizeof(uint16_t)];
}u;
ptr=buff;
/* reach end x==0 */
memcpy(u.c,ptr,sizeof(uint16_t));
while(u.x){ptr+=sizeof(u.x)+u.x;memcpy(u.c,ptr,sizeof(uint16_t));}/*can jump easily! word2word*/
/* */
u.x=x;
memcpy(ptr,u.c,sizeof(uint16_t));
ptr+=sizeof(u.x);
memcpy(ptr,word,u.x);
ptr+=u.x;
memset(ptr,0,sizeof(uint16_t));/*end of buffer x=0*/
}
int main(void){
char buffer[1024];
memset(buffer,0,sizeof(uint16_t));/*first x=0 because its empty*/
addword(buffer,"test",4);
addword(buffer,"yay",3);
addword(buffer,"chinchin",8);
printword(buffer);
return 0;
}