由于实际生产需要,软件系统的运行,会产生大量的日志文件,有时候一天就能产生超过百万条log记录,那么为了能够处理日志文件,查询并且找到我们想要的报错信息,因此不得不考虑怎么实现,打开大日志文件的可行方法。
在这里我采用的是内存映射的方式去读取文件的日志信息。代码部分如下所示:
cpp
QFile file(big_path);
qint64 fileSize = file.size(); // 获取文件的大小
uchar *data = file.map(0, fileSize); // 将文件的全部内容映射到内存中,返回一个指向该内容的指针
file.close();//文件的关闭不会影响到我们后续的内存映射部分。
if (data) { // 如果映射成功
QElapsedTimer timer;
timer.start();
message.clear();
QString text = QString::fromUtf8((char *)data, fileSize);
file.unmap(data); // 取消映射
ui->plainTextEdit->appendPlainText(text);
message=text.split("\n");
ui->label->setText("识别完成,时间为:"+QString::number(timer.elapsed()/1000)+"s");
QTextCursor cursor = ui->plainTextEdit->textCursor();
cursor.movePosition(QTextCursor::Start);
ui->plainTextEdit->setTextCursor(cursor);//是为了实现将鼠标对应的光标移动到第一行,也就是日志的最上面。
}
else { // 如果映射失败
qDebug() << "映射失败,错误信息:" << file.errorString(); // 打印错误信息
QMessageBox::information(this,"提示","映射失败,错误信息:"+file.errorString());
}
QT里面的内存映射的机制如下:
内存映射(Memory Mapping)是一种将文件或者设备的一部分映射到进程的虚拟地址空间的技术,这样可以方便地对文件或者设备进行读写操作,而不需要使用系统调用或者缓冲区。QT提供了QFileDevice类和QFile类来支持内存映射的功能,相关的方法有:
- map(qint64 offset, qint64 size, QFileDevice::MemoryMapFlags flags = NoOptions):这个方法可以将文件或者设备的一部分映射到内存中,并返回一个指向该内存区域的指针。参数offset表示映射的起始位置,size表示映射的大小,flags表示映射的选项,比如是否保护、是否共享等。
- unmap(uchar *address):这个方法可以取消内存映射,并释放相关的资源。参数address表示要取消映射的内存区域的指针。
- isMapped(uchar *address):这个方法可以检查一个内存区域是否是由map()方法映射的。参数address表示要检查的内存区域的指针。
除此之外,还有如何搜寻自己想要的信息,方式有以下几种:
首先,第一种是遍历循环每一条log信息,并在其中进行搜索,但是这样的搜索方式只能用于小日志文件,当文件内容过多的时候,这种搜索方式的时间度是很大的。
利用for或者while等循环,来遍历每一条的log信息。但是这个遍历出来的速度和效率是十分慢的。
代码实现如下所示:
qDebug()<<pp;
path_text=pp;
message.clear();
// 创建一个QFile对象,关联用户选择的文件
QFile file(path_text);
// 以只读模式打开文件
if (file.open(QIODevice::ReadOnly)) {
// 循环读取每一行
// 创建一个QTextStream对象,关联文件
QTextStream in(&file);
// 设置流对象的编码为UTF-8
in.setCodec("UTF-8");
while (!file.atEnd()) {
// 读取一行内容,转换为QString类型
QString line = QString::fromUtf8(file.readAll());
// 处理或显示每一行内容
message.append(line);
qDebug()<<line;
ui->plainTextEdit->appendPlainText(line);//appendPlainText
}
// 关闭文件
file.close();
}
else{
qDebug() <<"error";
}
这里为了加快读取的速度,还专门设置了文件的编码形式为UTF-8,来减少QT的自动识别编码的时间,而且这个读取文件的方式是用的Qtextstream的方式来的。
第二种就是,由于笔记本上面自带的软件记事本而想到的一种方式,就是记事本的查询功能的实现,因此考虑到,将同样的功能实现在QT里面。
另外,上面也说过了需要实现log文件的显示,在这里我采用的是Qplaintext控件来显示大量的文本信息。注意在这里不能采用textedit编辑器来显示大容量的文本,它会出现错误。
我的界面设计如下所示:
因为我需要查找信息,因此,要将用户的输入的信息进行筛选,所以我还用了一个linedit的控件来获取输入内容。
实现查找信息的功能的代码部分如下图所示:
cpp
QString goal=ui->lineEdit->text();
bool result=ui->plainTextEdit->find(goal);//向下寻找
// bool result2=ui->plainTextEdit->find(goal,QTextDocument::FindBackward);//向回找
if(result)
{
QTextCursor cursor = ui->plainTextEdit->textCursor();
qDebug()<<cursor.blockNumber();
shunxu.push_back(cursor.blockNumber());
return true;
}
return false;
我通过QT给的封装函数,find函数,它会帮助我找到符合我需要的内容的所在下一行,并且将光标移动到这一行,然后我再利用QTextCursor来获取当前光标所在行数的位置,并且打印保存下来。
因为我在前面的ui->plaintext里面,将获取得到的log内容,通过QString里面的split("\n")函数的方式将原来的QString的内容按行分割成QStringList的形式保存下来。然后我通过前面获得的行号,将对应的Qtring的行的内容,取出来,并且显示ui->plaintext上面即可。
这种遍历方式也最多只能达到同时遍历几百行的样子。
第二种方式也可以优化,比如可以分成两部分,多开一个线程,让其中一个从最后开始寻找,主线程从第一行开始寻找,最后将找到的日志行数汇总到一起。相当于是一个简单的二分法寻找。
最后第三种方式:同时遍历很多行数
这个方法的时间复杂度是O(n),也就是随着元素的数量增加,所需的时间也会线性增加。如果您想要一次遍历很多行,也就是提高查找的效率,您可以使用以下的方法:
- 使用QHash或者QMap类来存储每个元素和它们的索引或者计数,这样可以实现O(1)或者O(log n)的查找时间,但是需要额外的空间来存储哈希表或者映射表。
- 使用QStringList类的filter()方法来过滤出包含指定内容的元素,然后使用indexOf()方法或者contains()方法来获取它们的索引或者计数,这样可以减少遍历的次数,但是需要创建一个新的QStringList对象。
- 使用QRegularExpression类来创建一个正则表达式对象,表示您想要查找的内容,然后使用QStringList类的indexOf()方法或者contains()方法来查找匹配该正则表达式的元素,并获取它们的索引或者计数,这样可以更灵活地定义查找的条件,但是需要注意正则表达式的语法和效率。
代码部分如下所示:
cpp
#include <QApplication>
#include <QPlainTextEdit>
#include <QDebug>
#include <QRegularExpression>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QPlainTextEdit *edit = new QPlainTextEdit(); // create a QPlainTextEdit object
edit->setPlainText("This is a test text for QPlainTextEdit.\nIt may have multiple lines.\nSome lines may contain the word function."); // set some plain text
QString text = edit->toPlainText(); // get the plain text content
QStringList lines = text.split("\n"); // split the text by newline
QRegularExpression re("\\bfunction\\b"); // create a regular expression object, using \b to match word boundaries
int count = 0; // the number of lines that match the regular expression
int index = -1; // the index of the first matching line
while ((index = lines.indexOf(re, index + 1)) != -1) { // loop through the lines, using indexOf() method to find the matching line
count++; // increase the count
qDebug() << "Found" << re.pattern() << "at line" << index + 1; // print the line number, add 1 because the index is zero-based
}
qDebug() << "Total" << count << "lines match" << re.pattern(); // print the total count
return app.exec();
}
QHash和QMap都是Qt提供的关联容器类,它们可以用来存储键值对的数据结构。它们的主要区别是:
- QHash是基于哈希表实现的,它的查找速度通常比QMap更快,但是它的键是以任意顺序存储的,而且它对键的类型有更多的要求,需要提供operator==()和qHash()函数。
- QMap是基于跳表实现的,它的查找速度通常比QHash慢一些,但是它的键是以升序顺序存储的,而且它对键的类型只需要提供operator<()函数。
如果您需要时间度最小的一种遍历方法,我建议您使用QHash,并且使用STL风格的迭代器来遍历。这样可以避免创建额外的对象,并且可以直接访问键和值。例如:
cpp
QHash<QString, QStringList> hash;
// insert some data into hash
QHash<QString, QStringList>::const_iterator i = hash.constBegin();
while (i != hash.constEnd()) {
QString key = i.key();
QStringList value = i.value();
// do something with key and value
++i;
}