我有一个使用大量字符串的应用程序。所以我有一些内存使用问题。 我知道在这种情况下最好的解决方案之一是使用数据库,但目前我不能使用它,所以我正在寻找其他解决方案。
在C#字符串中存储在Utf16中,这意味着与Utf8(对于我的字符串的主要部分)相比,我丢失了一半的内存使用量。 所以我决定使用utf8字符串的字节数组。但令我惊讶的是,这个解决方案在我的应用程序中占用了比简单字符串多两倍的内存空间。
所以我做了一些简单的测试,但我想知道专家的意见,以确保。
测试1:固定长度字符串分配
var stringArray = new string[10000];
var byteArray = new byte[10000][];
var Sb = new StringBuilder();
var utf8 = Encoding.UTF8;
var stringGen = new Random(561651);
for (int i = 0; i < 10000; i++) {
for (int j = 0; j < 10000; j++) {
Sb.Append((stringGen.Next(90)+32).ToString());
}
stringArray[i] = Sb.ToString();
byteArray[i] = utf8.GetBytes(Sb.ToString());
Sb.Clear();
}
GC.Collect();
GC.WaitForFullGCComplete(5000);
内存使用
00007ffac200a510 1 80032 System.Byte[][]
00007ffac1fd02b8 56 152400 System.Object[]
000000bf7655fcf0 303 3933750 Free
00007ffac1fd5738 10004 224695091 System.Byte[]
00007ffac1fcfc40 10476 449178396 System.String
正如我们所看到的,字节数组占用的内存空间减少了两倍,这里没有真正的惊喜。
测试2:随机大小的字符串分配(具有实际长度)
var stringArray = new string[10000];
var byteArray = new byte[10000][];
var Sb = new StringBuilder();
var utf8 = Encoding.UTF8;
var lengthGen = new Random(2138784);
for (int i = 0; i < 10000; i++) {
for (int j = 0; j < lengthGen.Next(100); j++) {
Sb.Append(i.ToString());
stringArray[i] = Sb.ToString();
byteArray[i] = utf8.GetBytes(Sb.ToString());
}
Sb.Clear();
}
GC.Collect();
GC.WaitForFullGCComplete(5000);
内存使用
00007ffac200a510 1 80032 System.Byte[][]
000000be2aa8fd40 12 82784 Free
00007ffac1fd02b8 56 152400 System.Object[]
00007ffac1fd5738 9896 682260 System.Byte[]
00007ffac1fcfc40 10368 1155110 System.String
String占用的空间少于字节数组内存空间的两倍。使用较短的字符串,我期待字符串的开销更大。 但似乎相反的是,为什么?
测试3:与我的应用程序对应的字符串模型
var stringArray = new string[10000];
var byteArray = new byte[10000][];
var Sb = new StringBuilder();
var utf8 = Encoding.UTF8;
var lengthGen = new Random();
for (int i=0; i < 10000; i++) {
if (i%2 == 0) {
for (int j = 0; j < lengthGen.Next(100000); j++) {
Sb.Append(i.ToString());
stringArray[i] = Sb.ToString();
byteArray[i] = utf8.GetBytes(Sb.ToString());
Sb.Clear();
}
} else {
stringArray[i] = Sb.ToString();
byteArray[i] = utf8.GetBytes(Sb.ToString());
Sb.Clear();
}
}
GC.Collect();
GC.WaitForFullGCComplete(5000);
内存使用
00007ffac200a510 1 80032 System.Byte[][]
00007ffac1fd02b8 56 152400 System.Object[]
00007ffac1fcfc40 5476 198364 System.String
00007ffac1fd5738 10004 270075 System.Byte[]
这里的字符串占用的内存空间比字节少得多。这可能会令人惊讶,但我认为空字符串只引用一次。是吗?但我不知道这是否可以解释所有巨大的差异。还有其他原因吗?什么是最好的解决方案?
答案 0 :(得分:5)
这可能会令人惊讶,但我认为空字符串只被引用一次。
是的,空StringBuilder
会返回string.Empty
作为结果。下面的代码段打印True
:
var sb = new StringBuilder();
Console.WriteLine(object.ReferenceEquals(sb.ToString(), string.Empty));
但我不知道这是否可以解释所有巨大的差异。
是的,这完美地解释了它。您将保存5,000个string
个对象。字节差异大约为270,000-(198,000 / 2),因此大约为170 kBytes。除以5,每个对象得到34个字节,大致相当于32位系统上指针的大小。
什么是最佳解决方案?
做同样的事情:让自己成为一个private static readonly
空数组,并在每次从string.Empty
获得sb.ToString()
时使用它:
private static readonly EmptyBytes = new byte[0];
...
else
{
stringArray[i] = Sb.ToString();
if (stringArray[i] == string.Empty) {
byteArray[i] = EmptyBytes;
} else {
byteArray[i] = utf8.GetBytes(Sb.ToString());
}
Sb.Clear();
}