Kraken代码阅读(一)
源码DerrickWood/kraken: Kraken taxonomic sequence classification system (github.com)
文件quickfile.hpp
#ifndef QUICKFILE_HPP
#define QUICKFILE_HPP
#include "kraken_headers.hpp"
namespace kraken {
class QuickFile {
public:
QuickFile();
QuickFile(std::string filename, std::string mode="r", size_t size=0);
~QuickFile();
void open_file(std::string filename, std::string mode="r", size_t size=0);
char *ptr();
size_t size();
void load_file();
void sync_file();
void close_file();
protected:
bool valid;
int fd;
char *fptr;
size_t filesize;
};
}
#endif
文件quickfile.cpp
1.构造函数QuickFile()
#include "kraken_headers.hpp"
#include "quickfile.hpp"
using std::string;
namespace kraken {
QuickFile::QuickFile() {
valid = false;
fptr = NULL;
filesize = 0;
fd = -1;
}
有效位valid置无效,fptr指向文件内容的指针置空,filesize文件大小置0,fd文件描述符,用于文件操作,置-1。
QuickFile::QuickFile(string filename_str, string mode, size_t size) {
open_file(filename_str, mode, size);
}
string filename_str: 要打开的文件的名称。
string mode: 打开文件的模式,可以是"r"(只读)、“w”(读写,如果文件不存在则创建,如果存在则截断)、“rw”(读写,如果文件不存在则创建)。
size_t size: 文件的大小,当以写入模式打开文件并且文件需要创建时使用。
2.open_file函数
const char *filename = filename_str.c_str();
将filename_str转换为C风格字符串,以便可以使用在后续系统调用中。
int o_flags = mode == "w"
? O_RDWR | O_CREAT | O_TRUNC
: mode == "r" ? O_RDONLY : O_RDWR;
根据mode参数设置打开文件的标志(o_flags)。如果模式是"r",则设置O_RDONLY表示只读;如果模式是"w",则设置O_RDWR | O_CREAT | O_TRUNC表示读写、创建和截断;否则默认为O_RDWR表示读写。
int m_flags = mode == "r" ? MAP_PRIVATE : MAP_SHARED;
根据mode参数设置内存映射的标志(m_flags)。如果模式是"r",则使用MAP_PRIVATE表示私有映射,否则使用MAP_SHARED表示共享映射。
fd = open(filename, o_flags, 0666);
尝试打开文件,0666是文件权限,表示所有用户都有读写权限。
if (fd < 0 && mode == "rw" && errno == ENOENT) {
o_flags |= O_CREAT;
fd = open(filename, o_flags, 0666);
}
如果以"rw"模式打开文件并且文件不存在(errno == ENOENT),则再次尝试打开文件,这次包括O_CREAT标志以创建文件。
if (fd < 0)
err(EX_OSERR, "unable to open %s", filename);
如果文件打开失败,则打印错误消息并退出。
if (o_flags & O_CREAT) {
if (lseek(fd, size - 1, SEEK_SET) < 0)
err(EX_OSERR, "unable to lseek (%s)", filename);
if (write(fd, "", 1) < 0)
err(EX_OSERR, "write error (%s)", filename);
filesize = size;
}
else {
struct stat sb;
if (fstat(fd, &sb) < 0)
err(EX_OSERR, "unable to fstat %s", filename);
filesize = sb.st_size;
}
lseek函数用于设置文件描述符fd的当前位置。这里,它试图将位置设置为文件大小的最后一个字节(size - 1)。
SEEK_SET表示相对于文件的开始位置进行偏移。
如果lseek调用失败(返回值小于0),则打印错误消息并退出。
write函数尝试在当前文件位置写入一个空字符串(只有一个空字符)。
这实际上是在文件中创建一个占位符,以确保文件至少有size个字节。
如果写入失败(返回值小于0),则打印错误消息并退出。
设置filesize变量为用户指定的size,表示文件应该有的大小。
定义一个stat结构体变量sb,用于存储文件的状态信息。
fstat函数获取文件描述符fd引用的文件的状态信息,并将它们存储在sb中。
如果fstat调用失败(返回值小于0),则打印错误消息并退出。
设置filesize变量为sb.st_size,这是通过fstat获得的文件的实际大小。
fptr = (char *)mmap(0, filesize, PROT_READ | PROT_WRITE, m_flags, fd, 0);
使用mmap将文件映射到内存中,以便可以像访问普通内存一样访问文件内容。
madvise(fptr, filesize, MADV_WILLNEED);
使用madvise系统调用,建议内核预读映射的文件区域,因为预期会需要这些数据。
if (fptr == MAP_FAILED)
err(EX_OSERR, "unable to mmap %s", filename);
如果内存映射失败,则打印错误消息并退出。
valid = true;
设置valid标志,表示文件已成功打开并映射。
open_file函数目的是为了打开一个文件,并根据提供的参数以特定的模式进行操作。
3.load_file函数
它旨在将文件内容加载到内存中,并尝试使用内存锁(mlock)来确保文件内容被锁定在物理内存中。
if(mlock(fptr, filesize) != 0) {
这里,mlock函数尝试将文件指针fptr指向的内存区域(大小为filesize)锁定在物理内存中。如果mlock调用失败(返回非零值),则执行接下来的代码块。
int thread_ct = 1;
int thread = 0;
#ifdef _OPENMP
int old_thread_ct = omp_get_max_threads();
if (old_thread_ct > 4)
omp_set_num_threads(4);
thread_ct = omp_get_max_threads();
#endif
这部分代码设置了线程的数量。如果没有定义_OPENMP(即OpenMP没有被启用),则默认使用一个线程。如果启用了OpenMP,则尝试获取当前的最大线程数,并将其限制为最多4个线程,然后将thread_ct设置为新的最大线程数。
size_t page_size = getpagesize();
getpagesize函数返回系统的页面大小,这对于内存对齐和操作很有用。
char buf[thread_ct][page_size];
为每个线程分配一个缓冲区,大小等于页面大小。
#ifdef _OPENMP
#pragma omp parallel
#endif
{
#ifdef _OPENMP
thread = omp_get_thread_num();
#endif
...
}
如果启用了OpenMP,这段代码将启动一个并行区域,其中每个线程将执行接下来的代码块。
#ifdef _OPENMP
#pragma omp for schedule(dynamic)
#endif
for (size_t pos = 0; pos < filesize; pos += page_size) {
size_t this_page_size = filesize - pos;
if (this_page_size > page_size)
this_page_size = page_size;
memcpy(buf[thread], fptr + pos, this_page_size);
}
这是一个并行循环,它将文件分割成多个页面大小块,并使用memcpy将每个块复制到相应线程的缓冲区中。schedule(dynamic)表示循环迭代将动态分配给线程。
#ifdef _OPENMP
omp_set_num_threads(old_thread_ct);
#endif
如果之前改变了线程数,现在将其恢复到原始值。
4.ptr函数
char * QuickFile::ptr() {
return valid ? fptr : NULL;
}
这个函数的作用是,如果QuickFile对象处于有效状态,则提供对文件内容的直接访问;如果对象无效,则返回NULL,表示没有可用的文件内容指针。
5.size函数
size_t QuickFile::size() {
return valid ? filesize : 0;
}
这个函数的作用是,如果QuickFile对象处于有效状态,则返回文件的实际大小;如果对象无效,则返回0。
6.sync_file函数
void QuickFile::sync_file() {
msync(fptr, filesize, MS_SYNC);
}
msync(fptr, filesize, MS_SYNC); 是对msync系统调用的调用,它执行以下操作:
fptr:这是一个指向内存映射文件区域的指针。fptr应该是之前通过mmap或类似函数映射文件到内存的指针。
filesize:这是要同步的内存区域的大小,即文件的大小。
MS_SYNC:这个标志告诉msync将所有脏页(即内存中被修改但尚未写回磁盘的页面)同步到磁盘。这意味着调用将阻塞,直到所有数据都被写入磁盘。
msync用于将内存映射的文件区域的数据写回到对应的磁盘文件中。这个调用确保了内存中的数据与磁盘上的数据保持一致。
7.析构函数和close_file函数
QuickFile::~QuickFile() {
close_file();
}
调用关闭文件的函数。
void QuickFile::close_file() {
if (! valid)
return;
这行代码检查 valid 成员变量。
sync_file();
这行代码调用 sync_file 成员函数,其目的是将内存中所有修改过的数据同步到磁盘。这样做是为了确保所有对文件的更改都被保存,即使程序崩溃也不会丢失数据。
munmap(fptr, filesize);
munmap 是一个系统调用,用于解除之前通过 mmap 映射到进程地址空间的内存映射。fptr 是映射区域的起始地址,filesize 是映射区域的大小。解除映射后,内存中的数据将不再与文件内容相关联。
close(fd);
close 是一个系统调用,用于关闭文件描述符 fd。文件描述符是操作系统用来表示打开文件的一个整数。关闭文件描述符后,该描述符不再指向任何文件,并且可以重新用于其他文件。
valid = false;
最后,将 valid 设置为 false,表示 QuickFile 对象不再与任何打开的文件关联。
8.readable_fs函数
char* readable_fs(double size/*in bytes*/, char *buf) {
int i = 0;
double size:文件的大小,以字节为单位。
char *buf:一个字符数组,用于存储转换后的字符串。
const char* units[] = {"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"};
这是一个字符串数组,包含了从字节到尧字节的各种存储单位。
while (size > 1024) {
size /= 1024;
i++;
}
这个循环将 size 不断除以 1024,直到它小于或等于 1024。每次除法操作后,size 表示的大小减少一个单位(例如从字节到千字节),同时 i 递增,用于跟踪当前单位在 units 数组中的索引。
sprintf(buf, "%.*f %s", i, size, units[i]);
sprintf 是一个格式化字符串函数,用于将 size 转换为字符串,并附加正确的单位。%.*f 是一个格式说明符,其中 .* 允许指定小数点后的位数,i 的值用于控制小数点后的精度。因此,如果 i 是 2,size 将被格式化为两位小数的字符串。
return buf;
函数返回 buf 的指针,它指向包含人类可读文件大小的字符串。
总之,readable_fs 函数通过循环除以 1024 来减少文件大小的数值,同时递增单位索引,最后使用 sprintf 将结果格式化为易于理解的字符串。
9.slurp_file函数
std::vector<char> slurp_file(std::string filename, size_t lSize) {
FILE * pFile;
size_t result;
std::string filename:要读取的文件的名称。
size_t lSize:文件的大小。如果传递的是 0 或负值,函数将计算文件的实际大小。
FILE * pFile:一个指向 FILE 结构的指针,用于文件操作。
size_t result:用于存储 fread 的返回值,表示实际读取的字节数。
pFile = fopen ( filename.c_str() , "rb" );
使用 fopen 函数尝试以二进制读模式打开文件。filename.c_str() 将 std::string 对象转换为 C 风格的字符串,因为 fopen 需要一个 C 字符串作为参数。
if (pFile==NULL) {fputs ("File error",stderr); exit (1);}
如果 fopen 返回 NULL,则表示文件打开失败。函数将错误信息写入标准错误输出,并使用 exit 函数退出程序
if (lSize > 0) {
fseek (pFile , 0 , SEEK_END);
lSize = ftell (pFile);
rewind (pFile);
}
fseek 将文件指针移动到文件末尾,ftell 返回文件指针的当前位置,即文件的大小。然后 rewind 将文件指针重置到文件开始。
char buf[50];
readable_fs(lSize, buf);
std::cerr << "Getting " << filename << " into memory (" << buf << ") ...";
这部分代码使用之前定义的 readable_fs 函数将文件大小转换为可读的格式,并打印出一条信息,告知用户正在将文件加载到内存中。
std::vector<char> buffer(lSize);
result = fread (buffer.data(), 1, lSize, pFile);
创建一个大小为 lSize 的 std::vector<char> 容器 buffer,然后使用 fread 函数从文件中读取 lSize 字节到 buffer 中。buffer.data() 返回指向向量内部数据的指针。
if (result != lSize) {fputs ("Reading error",stderr); exit (3);}
如果 fread 读取的字节数不等于 lSize,表示读取过程中可能发生了错误。函数将错误信息写入标准错误输出,并退出程序。
fclose (pFile);
使用 fclose 函数关闭之前打开的文件。
std::cerr << " Done" << std::endl;
打印一条消息,告知用户文件读取完成。
return(buffer);
函数返回包含文件内容的 std::vector<char> 容器。
总的来说,slurp_file 函数用于将整个文件内容一次性读取到内存中,并以 std::vector<char> 的形式返回,便于后续处理。
总结
这个程序的主要目的是提供一个文件操作库,该库包含一个名为 QuickFile 的类,用于高效地读写文件,以及一个名为 slurp_file 的函数,用于快速加载文件内容到内存中。
原文地址:https://blog.csdn.net/2302_77272988/article/details/140561293
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!