这个文件有两个类:
- AppendFile
这个类主要是将数据写入到文件 - ReadSmallFile
这个类主要是将文件内容读取出来
AppendFile
成员变量
- FILE* _fp
打开的流 - char _buffer[64*1024]
打开流的缓冲区 - size_t wtirrenBytes
已经添加的长度
构造函数
该类的构造函数代开一个文件流,并为这个流设置缓冲区。
AppendFile(StringArg filename)
:_fp(fopen(filename.c_str(), "ae")),
_writtenBytes(0)
{
assert(fp_);
::setbuffer(_fp, _buffer, sizeof _buffer);
}
这里fopen的mode使用的ae
,a
表示以添加的方式打开流。
而e
书上没有,代码中注释以O_CLOEXEC
打开文件,说是当exce()
以后,文件流自动关闭,还是原子操作。
不懂。
fopen的打开的文件,如果不存在,那么直接会创建一个。
setbuffer()和setbuf():后者少一个参数,缓冲区的长度。
这两个函数都是设置缓冲为全缓冲。也就是当缓冲区被填满以后才会实际写到文件中。
还有一个setvbuf()这个函数参数更多,通过mode
可以设置缓冲的类型。
write()
该函数执行向流中写数据的操作,但不直接调用该函数
size_t write(const char* log, size_t len)
{
return fwrite_unlocked(log, 1, len, _fp);
}
fwrite_unlocked(void *, size_t , size_t , FILE )和fwrite()是相同的函数,前者不是线程安全的。
作用是,一次读写一个结构,而且可以包含有NULL。
第一个参数是一个void,通常是数组,之类的。
第二个参数是,一个结构体的大小,第三个参数是写多少个结构体
第四个参数是要写入的流。
总是从头开始写,如果要指定从哪一个开始写,可以在第一个参数转换前加上偏移量。
append()
该函数执行实际的写操作,同时确保数据完全写入
int append(const char * log, size_t len)
{
size_t n=write(log,len);
size_t remain=len-n;
//如果没有写完,那么循环往里写入
while(remain>0)
{
size_t nRe=write(log+n,remain); //加偏移量
if(nRe=0)
{
int err=ferror(_fp);
if(err)
{//输出错误信息,继续执行}
break;
}
n+=nRe;
remain=len-n;
}
_writtenBytes+=len;
}
该函数想文件流中写数据,让有数据没写完时,使用循环向流中写入,当剩余为0
时没判断是否出错。
在循环中,使用指针+偏移量的方式指定从第几个结构体开始写。
最后,将总写入的累加。
flush()
该函数提供手动将缓冲区数据写入到文件
void flush()
{
::fflush(_fp);
}
还有析构函数关闭流
以上就是该类的全部。
ReadSmallFile
该类提供从文件中读取数据的功能
成员变量
- char _buf[]
缓冲区 - int _fd
文件描述符 - int _err
错误代码
构造函数
构造函数打开文件,只读打开同时也是o_CLOEXCE
。并将缓冲区第一个元素设置为\0
。为啥要设置?
ReadSmallFile(StringArg filename)
:fd_(::open(filename.c_str(), O_RDONLY | O_CLOEXEC)),
err_(0)
{
_buf[0]='\0'; //这里为什么要设置?
if(fd<0){ _err=errno;}
}
析构函数
关闭文件描述符
readToString()
该函数读取文件内容,并想和窜入的参数填充数据,文件内容,创建时间等信息。
int readToString(int maxSize, String *content, int64_t *fileSize, int64_t *modifyTime, int64_t *createTime)
{
//除了前两个,后面的都可以为NULL
assert(content != NULL);
if (_fd > 0)
{
struct stat statbuf;
//填充文件信息
if (::fstat(_fd, &statbuf) == 0)
{
//如果是普通文件。
if (S_ISREG(statbuf.st_mode))
{
*fileSize = statbuf.st_size;
//调整容器的大小到文件的大小
content->reserve(static_cast(std::min(implicit_cast(maxSize), *fileSize)));
}
//如果是文件夹
else if (S_ISDIR(statbuf.st_mode))
{
err = EISDIR;
}
//如果传入了,那么就填充
if (modifyTime)
{
*modifyTime = statbuf.st_mtime;
}
if (createTime)
{
*createTime = statbuf.st_ctime;
}
}
else
{
err = errno;
}
//文件信息填充好以后,开始将文件的内容读取到给定的结构context中。
while (context.size() < maxSize)
{
//每次读取的大小是,剩余最大和缓冲区的大小。
size_t toRead = std::min(maxSize - context.size(), sizeof(_buf));
size_t n = read(_fd, _buf, toRead);
if (n > 0)
{
context.append(_buf, n);
}
else
{c_
if (n < 0)
{
err = errno;
}
break;
}
}
}
return err;
}
第一个参数的意义应该是给定,最大要读取的大小。
函数前半部分,读取文件信息,然后对给定的参数进行填充。
同时设置context的大小为,maxSize和文件大小中较小的那个。
然后后面同样使用while循环进行填充数据。
readToBuffer()
这个函数只是读取_buf大小的数据。同样使用循环。
int readToBuffer(int* size)
{
int err=0;
if(_fd>0)
{
size_t n=pread(_fd,_buf,size(_buf)-1,0);
if(n>=0)
{
if(size)
{
*size=n;
}
buf[n]='\0';
}else{
err=errno;
}
}
return err;
}
pread函数和read函数是同一个功能,但是pread函数多一个参数,是一个偏移量。同时还是一个原子操作。
这个文件夹中还有一些函数。范型的,没有定义,没看。
注
这里使用的参数stringArg全都是muduo自己封装的类。
类中,只有构造函数,拷贝复制函数,c_str()
,同时还有一个成员变量char *
。
为了效率不择手段啊。