".NET中的字符串和字符类型是如何存储在内存中的?"

11

我需要存储一个语言代码字符串,例如“en”,它始终包含2个字符。

把类型定义为"String"还是"Char"更好?

private string languageCode;

vs

的翻译是:vs。
private char[] languageCode;

还有其他更好的选择吗?

这两个变量在内存中是如何存储的?当值被分配时,会分配多少字节或位?


5
你是否已经证明这确实是一个问题?我发现在使用字符串时几乎没有必要担心内存,尤其是像这样小的字符串。如果它并没有表现出问题,那么就不要担心,直到它成为问题为止。如果字符串引起了内存问题,稍后很容易进行修复。否则,请使用字符串并且不用考虑内存问题。 - Russell Troywest
我有一个非常复杂的逻辑,它会在内存中存储成千上万个这样的对象,因此每一点点的帮助都很重要。 - The Light
@William 如果性能如此重要,为什么不声明一个 enum LanguageCode : short 并节省 2 个字节呢? - Adam Houldsworth
可能会有多达20种组合。不确定这样的枚举在内存中是如何存储的,或者是否更好的方式? - The Light
@William,在这种情况下,使用byte,它只占用1个字节。我已经更新了我的答案。枚举是值类型,在方法作用域内存在于堆栈上,或者在类作用域内的堆空间中存在。 - Adam Houldsworth
6个回答

8

存储方式

stringchar[]都存储在堆上-因此存储方式是相同的。在内部,我会认为string只是char[]的封装,具有大量额外的代码使其对您有用。

另外,如果有许多重复的字符串,您可以利用托管池来减少这些字符串的内存占用。

更好的选择

我更喜欢使用string - 它立即更清晰地显示数据类型及其使用方法。人们也更习惯使用字符串,因此可维护性不会降低。您还将从为您完成的所有样板代码中获得很大的益处。Microsoft还付出了很多努力,以确保string类型不会成为性能障碍。

分配大小

我不知道分配了多少,但我相信字符串非常高效,因为它们仅分配足以存储Unicode字符的空间-由于它们是不可变的,因此这样做是安全的。另外,数组无法重新调整大小而不分配新数组中的空间,因此我会再次假设它们仅获取所需内容。

.NET数组的开销?

替代方案

根据您的信息,只有20个语言代码且性能至关重要,因此您可以声明自己的枚举以减少表示代码所需的大小:

enum LanguageCode : byte
{
    en = 0,
}

这只需要1个字节,而不是一个数组中两个char所需的4个以上。但是这限制了可用的LanguageCode值的范围为byte的范围——对于20个项目来说,这足够大了。
使用sizeof(LanguageCode)操作符可以查看值类型的大小。枚举本质上只是底层类型,它们默认为int,但是如您在我的代码示例中所见,可以通过“继承”新类型来更改默认类型。

在 .Net 中,您不需要显式地对字符串进行内部化;它们仅通过声明就会被隐式地内部化。此外,在 .Net 中,字符串和字符数组非常不同,因为字符数组是可变结构,可以位于堆栈上,也可以位于堆上,具体取决于您如何声明它们,而字符串是不可变的。正如您链接的文章所指出的那样,字符串在内部池中不断地累积,而不是在常规的 .Net Framework 内存中 - 这意味着它们可能非常浪费空间。 - Chris Moschini
@ChrisMoschini 并非所有字符串都被内部化。字面量被内部化,但其他大部分内容并没有。如果您输入一个字符串,从资源文件或其他来源读取它们,则它们不会被内部化。您必须手动将它们内部化。有趣的是,我的回答甚至没有明确说明这一点。 - Adam Houldsworth
这取决于代码的编写方式 - 例如,如果它正在搜索在代码中声明的一堆字符串位,您仍然会得到一堆内部化的字符串。但是重要的性能问题是在不需要它们的情况下将大量不必要的中间字符串抛到堆上 - 单个char数组在内存方面始终更便宜,如果您编写的代码类似于Regex的内部,则在CPU方面更便宜。在.Net中使用的内存越少,GC也就越少,这还有另一个CPU的好处。 - Chris Moschini
@chris 的确,但这取决于一个可能不会调整大小的数组,比如问题中的语言代码。然而,在问题本身的情况下,我实际上建议绕过所有这些,并使用枚举值。 - Adam Houldsworth
阅读文章https://dev59.com/a3A65IYBdhLWcg3wuRF8 后,我有点困惑链接在内部是如何存储的。 - Bhaktuu

4

简短回答:使用字符串

详细回答:

private string languageCode;

据我所知,字符串被存储为一个长度为前缀的字符数组。为了维护这个原始数组,堆上实例化了一个String对象。但是,String对象不仅仅是一个简单的数组,它还可以执行基本的字符串操作,如比较、连接、子字符串提取、搜索等。
虽然...
private char[] languageCode;

这段文字将会被存储为字符数组,也就是说,一个Array对象将会被创建在堆上并被用于管理你的字符。但它仍然有一个长度属性,该属性存储在内部,因此与字符串相比,没有明显的内存节省。尽管一个数组可能比一个字符串简单,并且可能具有较少的内部变量,从而提供较低的内存占用(这需要验证)。

但另一方面,您失去了在这个字符数组上执行字符串操作的能力。甚至像字符串比较这样的操作现在都变得麻烦了。所以,长话短说,还是使用字符串吧!


1
这两个变量在内存中如何存储?当赋值时,会分配多少字节或位给它们?
在.NET中,每个实例都是按以下方式存储的:一个IntPtr大小的字段用于类型标识符;另一个用于对实例进行锁定;其余部分是实例字段数据向上舍入到IntPtr大小的数量。因此,在32位平台上,每个实例占用8个字节+字段数据。
这适用于string和char[]。这两者也将数据长度作为IntPtr大小的整数存储,然后是实际数据。因此,在32位平台上,一个包含两个字符的字符串和一个包含两个字符的char[]将占用8 + 4 + 4 = 16个字节。
当存储恰好两个字符时,唯一减少此空间的方法是将实际字符或包含字符的结构体存储在字段或数组中。所有这些只会消耗4个字节用于字符。
// Option 1
class MyClass
{
    char Char1, Char2;
}

// Option 2
class MyClass
{
    CharStruct chars;
}
...
struct CharStruct { public char Char1; public char Char2; }

MyClass 在 32 位机器上每个实例将使用 8 字节加上 4 字节的字符。

// Option 3
class MyClass
{
    CharStruct[] chars;
}

这将使用8个字节作为MyClass的开销,再加上4个字节用于chars引用,再加上12个字节用于数组的开销,再加上每个数组中CharStruct所需的4个字节。


有趣。不过你这些信息是从哪里得到的呢? - kristianp
1
@kristianp 大多数信息来自于这篇 MSDN 文章: https://msdn.microsoft.com/zh-cn/magazine/cc163791.aspx (向下滚动至图6) - Roman Starkov

0

字符串确实有一个指针长度的大小开销,即32位进程为4个字节,64位进程为8个字节。但是,与字符数组相比,字符串提供了更多的功能。

如果您的应用程序使用许多短字符串,并且您不需要经常使用它们的字符串属性和方法,那么您可能可以节省一些内存空间。但是,如果您想将任何一个字符串用作字符串,您首先必须创建一个新的字符串实例。我无法看出这如何帮助您节省足够的内存以值得麻烦。


0

String只是在内部实现了一个char类型的索引器,可以说string就相当于具有大量额外代码使其对您有用的char[]类型,因此像数组一样,它总是存储在堆上。

如果不分配新空间,则无法操作数组,字符串也是如此,因此它是不可变的。

String实现了IEnumerable<char>

值得注意的一点:当您将字符串传递给函数时,它是按值传递,除非使用ref


0
如果您想要存储恰好2个字符,并且希望效率最高,请使用结构体:
struct Char2
{
 public char C1, C2;
}

使用这个结构体通常不会导致新的堆分配。它只会增加现有对象的大小(尽可能少),或者占用非常便宜的栈空间。

堆分配完全取决于您声明结构的位置。如果在方法/属性内部声明,则仅在堆栈上。在类内部,它将位于堆中,与其他类成员一起。 - Adam Houldsworth
它不会导致新的分配。它只会将现有对象的大小增加(尽可能少)。 - usr
是的,没错,但堆分配通常非常快,不应该一开始就担心它。话虽如此,struct LanguageCode 的结构体是一个不错的选择。 - Adam Houldsworth
那他为什么关心性能呢?也许他需要存储十亿个这样的字符串。这是一个性能问题,我将按照这种方式回答它。 - usr
他并不讨论性能问题,这个词没有被提及。他询问的是数据存储方式以及哪种更好,而你只是间接回答了后者。 - Adam Houldsworth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接