QT处理日志文件

由于实际生产需要,软件系统的运行,会产生大量的日志文件,有时候一天就能产生超过百万条log记录,那么为了能够处理日志文件,查询并且找到我们想要的报错信息,因此不得不考虑怎么实现,打开大日志文件的可行方法。

在这里我采用的是内存映射的方式去读取文件的日志信息。代码部分如下所示:

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的控件来获取输入内容。

实现查找信息的功能的代码部分如下图所示:

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()方法来查找匹配该正则表达式的元素,并获取它们的索引或者计数,这样可以更灵活地定义查找的条件,但是需要注意正则表达式的语法和效率。

代码部分如下所示:

#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风格的迭代器来遍历。这样可以避免创建额外的对象,并且可以直接访问键和值。例如:

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;
}

猜你喜欢

转载自blog.csdn.net/Helloorld_1/article/details/132338644
今日推荐