想象一下。您正在使用 C++03 并编写:
string a("hello");
string b = a;
此时您有两个字符串对象a
,b
每个对象都有自己的缓冲区来存储字符数组。即使缓冲区的内容完全相同,a
仍然b
有自己的“hello”副本。这是对内存的浪费。如果他们共享缓冲区,您将不得不使用单个字符数组来存储两个字符串的“hello world”。
现在使用 QString 有点不同:
QString a("Hello");
QString b = a;
在这种情况下,只a
创建了一个 char 数组来存储“hello”。b
而不是创建自己的 char 数组,而是简单地指向a
的 char 数组。这样可以节省内存。
现在,如果你这样做b[0]='M'
,id est,你修改b
,然后b
创建它自己的 char 数组,复制a
's 数组的内容,然后修改它自己的数组。
在 Java 中,字符串是不可变的对象。换句话说,Java 没有在String
类上提供任何方法来修改其内容。这样做是为了始终可以共享此类数据。
补充别人提到的东西:
我怎么知道我可以释放 char 数组?
这就是“参考计数”的用途。当一个对象被创建并设置为指向 char 数组时,它的引用计数将增加 1,因此它知道有多少对象仍在使用它。当指向它的对象被销毁时,引用计数会减少。当计数器达到零时,char 数组知道没有人在使用它,因此可以释放它。
这是引用计数的一个非常粗略的实现。无论如何,我并不打算准确或正确。我忽略了在 C++ 中实现复制构造函数和赋值运算符的正确方法。我无法检查实施是否有效。认为它是有点像 C++ 的算法描述。我只是想教这个概念。但是想象一下你有这些类:
class SharedData{
private:
int refcount;
int data;
public:
SharedData(int _data){data=_data;refcount=1;}
void incRef(){refcount++;}
void decRef(){--refcount; if(refCount==0) delete this;}
};
class Data{
SharedData* shared;
public:
Data(int i){shared = new Data(i);}
Data(const Data& data){shared = data.shared; shared->incRef();}
const Data& operator=(const Data& data){if(shared!=data.shared){
shared->decRef();
shared = data.shared;
shared->incRef();}
}
~Data(){shared->decRef();}
};
类的两个对象Data
可以共享同一个SharedData
对象,所以:
void someFunction() {
Data a(3) //Creates a SharedData instance and set refcount to 1
if (expression) {
Data b = a; //b points to the same SharedData than a. refcount is 2
b = Data(4);// b points to diferent SharedData. refcount of SharedData of a is decremented to 1 and b's SharedData has refcount 1
//destructor of b is called. Because shared data of b has now refcount == 0, the sharedData is freed;
}
//destructor of a is called, refcount is decremented again
// because it is zero SharedData is freed
}
因此资源使用最大化,副本最小化。两者都a
使用b
相同的SharedData
(又名 int 3
)。. 4
没有从 复制到a
,b
它们只是共享相同的数据。一个 int 没什么大不了的,但想象一下,如果SharedData
有一些大字符串或任何其他更复杂的数据结构。只复制一个指针比复制几十个字节要快得多。当你真的不需要副本时,它还可以节省大量内存。
什么是写时复制?
回想一下我在我们做的时候说过的话b[0]='M'
。那是写时复制。b
并且a
共享相同的字符数组。但b
需要修改字符串。它不能直接这样做,因为那也会修改字符串a
。因此b
必须创建自己的 char 数组副本才能更改它。因为它只需要在修改数组时创建副本,所以它被称为copy-on-write。