由于實際生產需要,軟件系統的運行,會產生大量的日志文件,有時候一天就能產生超過百萬條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-8in.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 objectedit->setPlainText("This is a test text for QPlainTextEdit.\nIt may have multiple lines.\nSome lines may contain the word function."); // set some plain textQString text = edit->toPlainText(); // get the plain text contentQStringList lines = text.split("\n"); // split the text by newlineQRegularExpression re("\\bfunction\\b"); // create a regular expression object, using \b to match word boundariesint count = 0; // the number of lines that match the regular expressionint index = -1; // the index of the first matching linewhile ((index = lines.indexOf(re, index + 1)) != -1) { // loop through the lines, using indexOf() method to find the matching linecount++; // increase the countqDebug() << "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 countreturn 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;
}