这个问题隐藏的很深,一般不容易察觉它造成的问题,而只是享受它提供的好处(节省内存,而且速度更快)。
但我发现它现在至少造成两个问题:
1. 把大量的QString放到QMap里,使用完毕后清空QMap,然而因为隐式共享的原因,实际上QString占用的大量内存得不到释放。这样程序积累了大量无用数据的内存,从此程序运行变得异常缓慢。
2. QFileInfo也有隐式共享问题,造成读取新文件信息的时候,估计要和旧文件信息全部对比一遍(就算是通过hash对比也很慢啊,我这里测试文件有11万个呢),确定没有这个新文件,然后才开始真正工作。这样效率极低。部分解决办法是使用const解决QFileInfo的问题。但是QString却不能使用const,因为我中间还要修改它。
只能慢慢研究了~
官方文档:
http://doc.qt.io/qt-4.8/implicit-sharing.html
http://doc.qt.io/qt-5/qstring.html
中文博客:
http://blog.csdn.net/yestda/article/details/17893221
http://blog.chinaunix.net/uid-27177626-id-3949985.html
http://blog.csdn.net/zhu_xz/article/details/6061201
------------------------------------------------------------------
隐式共享
说明对象在自操作前,指向一块共享的内存。
在自操作时,就会发生写时拷贝,让自己指向一块新的内存,这个内存也可以被其他对象引用,所以这个内存块有引用计数。当这块内存的引用计数为零时,会被回收。
所以,除非
QString *newString = new QString(theOldQString);
// then i forget to delete the newString
就会发生共享内存被引用但是却没被释放的问题。
其实隐式共享的内存确定可以被释放,但还是应用程序却仍然占据了高内存
这个就是你想要搞明白的问题
但是
for (int i=0; i<5000000; i++) {
QString str = "b";
list << str;
}
就不存在隐式共享的问题,会把整个对象放在list里
for (int i=0; i<5000000; i++) {
QString str = "aaaa";
list << str;
}
和
QString str = "aaaa";
for (int i=0; i<5000000; i++) {
list << str;
}
两段代码会使用完全不同的内存大小
------------------------------------------------------------------
for (int i=0; i<5000000; i++) {
QString str = "a";
list << str;
}
和
for (int i=0; i<5000000; i++) {
QString str = "aaaa";
list << str;
}
两段代码也会使用完全不同的内存大小,而且使用内存都特别大。
------------------------------------------------------------------
理论知识:
不同于 Java 风格遍历器,STL 风格遍历器直接指向元素本身。容器的begin()函数返回指向该容器第一个元素的遍历器;end()函数返回指向该容器最后一个元素之后的元素的遍历器。end()实际是一个非法位置,永远不可达。这是为跳出循环做的一个虚元素。如果集合是空的,begin()等于end(),我们就不能执行循环。
由于有隐式数据共享(我们会在后面的章节介绍该部分内容),即使一个函数返回集合中元素的值也不会有很大的代价。Qt API 包含了很多以值的形式返回QList或QStringList的函数(例如QSplitter::sizes())。如果你希望使用 STL 风格的遍历器遍历这样的元素,应该使用容器的拷贝,例如:
1
2
3
4
5
6
7
8
9
10
11
|
// 正确的方式
const QList<QString> sizes = splitter->sizes();
QList<QString>::const_iterator i;
for (i = sizes.begin(); i != sizes.end(); ++i)
...
// 错误的方式
QList<QString>::const_iterator i;
for (i = splitter->sizes().begin();
i != splitter->sizes().end(); ++i)
...
|
这个问题不存在于那些返回集合的 const 或非 const 引用的函数。隐式数据共享对 STL 风格遍历器造成的另外影响是,在容器上运行着非 const 遍历器的时候,不能对容器进行拷贝。Java 风格的遍历器没有这个问题。
foreach关键字
如果类型名中带有逗号,比如QPair<int, int="">,我们只能像上面一样,先创建一个对象,然后使用foreach关键字。如果没有逗号,则可以直接在foreach关键字中使用新的对象。
Qt 会在foreach循环时自动拷贝容器。这意味着,如果在遍历时修改集合,对于正在进行的遍历是没有影响的。即使不修改容器,拷贝也是会发生的。但是由于存在隐式数据共享,这种拷贝还是非常迅速的。
因为foreach创建了集合的拷贝,使用集合的非 const 引用也不能实际修改原始集合,所修改的只是这个拷贝。
参考:http://jukezhang.com/2014/11/23/learn-qt-eight/
参考:http://www.devbean.net/2013/01/qt-study-road-2-implicit-sharing/
http://www.devbean.net/2013/01/qt-study-road-2-iterator/