qt如何实现大文件的加载和显示

最近研究了下如何用qt的原生控件来加载和显示大文件(>1G),分享下一些摸索经验。

下文源码:loginsight

文件的内存映射

在开始qt部分之前,我们先了解一个概念——文件的内存映射。

我们知道一般读文件用到的API是fopen/fread/fclose,或者是open/read/close,这种方式都需要内核帮忙作一次拷贝。

linux中有一个函数叫mmap(windows也有类似功能),可以避免这样的一次拷贝。

请看这幅对比图(图片来源:https://www.jianshu.com/p/eece39beee20):

20200502212036330_2051853148.png

当我们用fread/read时,都是触发了一个步骤1的read系统调用,然后内核帮忙到磁盘中把请求的文件内容读取到kernnel buffer,然后再copy回用户进程空间。

相比,如果用mmap,一开始内核就把整个文件映射到了用户进程的虚拟内存中;映射过程只是分配了地址空间,并没有拷贝内存,所以速度快。这一段地址空间在代码层面看到的就是一块连续的内存,当代码访问这块内存,如果引发缺页异常,内核就会加载文件内存到buffer。这样就减少了一次内存拷贝。

使用mmap对于大文件的加载和显示有什么好处呢:

  • 读取速度快
  • 可以把整个文件当做代码中一个连续内存区域,直接以const char*访问,即可以透明地认为整个文件已经加载到进程内,且保存为一个字符串(指针)了。对于代码设计而言较方便。

mmap参考资料:
https://www.jianshu.com/p/eece39beee20
https://zhuanlan.zhihu.com/p/69555454

Qt里显示大文件

在Qt里,QFile::map提供了跨平台的“文件内存映射”支持。所以通过调用QFile::map就可以把文件“加载”为一个const char*字符串使用。

我们知道,在 QPlainTextEdit里,显示文本一般可以用setPlainText。如果直接把map后的内存传递给setPlainText会导致文件的所有内容被读入内存,这显然是不行的。

一般对大文件处理方式是“分页”,也就是一次只加载部分内容。

为了让用户感知不到文件被“分页”了,我们需要处理下,自动加载分页的内容。具体的做法:

  1. 监听滚动事件,自动加载下一个/上一个分页
  2. 隐藏滚动条,用外部滚动条替代;外部滚动条对应整个文件范围,并保持实时同步

思路

在开始实现前,我们最好有一个清晰的思路,可以建个简单的模型:

20200502221058917_2129086578.png

这里,我们把窗口可视区想象成一个固定高度的滑块,整个滑块可以在整个文件从头滑动到尾部——对应用户从第一行拉动滚动条(右侧灰色箭头)直到最后一行。

为了能减少滚动过程中频繁触发读取文件,可以设置一块预加载区域,比可见区域大。每次可见区域要滑出预加载区的时候,就触发一次预加载区的预读。

在实现上,预加载区域对应的就是setPlainText加载的内容,而可见区域的滚动就直接由QPlainText代为实现了。

于是,要实现大文件的加载和显示,只要:

  1. 预读内容,通过setPlainTextQPlainTextEdit
  2. 处理QPlainTextEdit的滚动事件,在即将滚出预读区的时候,更新预读区

当然,说起来容易,做起来还是要处理一些琐碎事务的。详见:https://github.com/compilelife/loginsight/blob/master/src/logtextedit.cpp

再谈文件的内存映射

当然,如果只是单纯地去显示一个大文件, 直接用常规的文件读写API也是可行的。map的优势还不够明显。

实际上,map在这个场景里,真正强大的地方是在于把文件当做“已经加载好的连续字符串”。在加载了大文件后,不可避免地需要做查找、定位等逻辑,这时使用map可同时优化效率和代码可读性。

比如,我们要在上面工作的基础上做全文搜索并定位到匹配行。这时QPlainTextfind因为只能搜索预加载内容,无法使用。而基于map,只需要对map后的内存地址,执行strstr按字符串查找,再把查找到的位置前后内容载入可视区即可。

总结

为了基于qt原生控件去高效地显示大文件,我们用了不少奇技淫巧,把QPlainTextEdit伪装成了支持大文件的文本框。也许下一步可以试试看用QPlainTextDocumentLayout实现自定义文本框,作更深入地优化。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354