【问题标题】:How are String and Char types stored in memory in .NET?String 和 Char 类型如何存储在 .NET 的内存中?
【发布时间】:2012-06-02 16:55:27
【问题描述】:

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

将类型定义为“String”还是“Char”更好?

private string languageCode;

private char[] languageCode;

或者还有其他更好的选择吗?

这 2 个如何存储在内存中?分配值时将分配多少字节或位?

【问题讨论】:

  • 你有没有向自己证明这是一个问题?我很少发现在使用字符串时需要担心内存——尤其是这么小的字符串。如果它没有显示为问题,那么在它出现之前不要担心它。如果字符串导致您出现内存问题,这在以后很容易解决。否则使用字符串,甚至不要考虑内存问题。
  • 我有一个非常严格的逻辑,可以在内存中存储数千个这样的内容,所以每一点都有帮助。
  • @William 如果性能如此关键,为什么不声明 enum LanguageCode : short 并节省 2 个字节?
  • 最多有 20 种组合。不确定这样的枚举是如何存储在内存中的,或者它是否更好?
  • @William 在这种情况下,选择byte,它需要 1 个字节。我已经更新了我的答案。枚举是值类型,并且在方法范围内时将存在于堆栈上,或者在类范围内的类的堆空间内。

标签: c# memory memory-management garbage-collection clr


【解决方案1】:

它们的存储方式

stringchar[] 都存储在堆上 - 所以存储是相同的。在内部,我会假设 string 只是 char[] 的封面,带有 很多 额外代码以使其对您有用。

另外,如果您有很多重复的字符串,您可以使用Interning 来减少这些字符串的内存占用。

更好的选择

我更喜欢字符串 - 数据类型是什么以及您打算如何使用它会更加明显。人们也更习惯于使用字符串,因此可维护性不会受到影响。您还将从为您完成的所有样板代码中受益匪浅。 Microsoft 也付出了很多努力来确保 string 类型不会影响性能。

分配大小

我不知道分配了多少,我相信字符串非常有效,因为它们只分配足够的空间来存储 Unicode 字符——因为它们是不可变的,所以这样做是安全的。如果不分配新数组中的空间,数组也无法调整大小,所以我再次假设它们只抓取所需的内容。

Overhead of a .NET array?

替代品

根据您的信息,只有 20 种语言代码并且性能是关键,您可以声明自己的枚举以减少表示代码所需的大小:

enum LanguageCode : byte
{
    en = 0,
}

这将只占用 1 个字节,而不是 4+ 两个 char(在一个数组中),但它确实将可用 LanguageCode 值的范围限制在 byte 的范围内 - 这不仅仅是很大足够 20 件物品。

您可以使用sizeof() 运算符查看值类型的大小:sizeof(LanguageCode)。枚举只是底层的类型,它们默认为int,但正如您在我的代码示例中看到的那样,您可以通过“继承”一个新类型来更改它。

【讨论】:

  • 你没有在 .Net 中明确地实习字符串;他们仅仅通过声明就隐含地为你实习。此外,字符串和 char 数组在 .Net 中非常不同,因为 char 数组是堆上的可变结构,甚至是堆栈上的可变结构,具体取决于您声明它们的方式,而字符串是不可变的,并且正如您链接到的文章所指出的那样,构建在实习生池而不是常规的 .Net Framework 内存中构建和构建——这意味着它们可能非常浪费。
  • @ChrisMoschini 并非所有字符串都被保留。文字是被实习的,但其他什么都不是。如果您将字符串作为输入,请从资源文件或其他不被实习的源中读取它。您必须手动实习。有趣的是,我的回答甚至没有说明任何一种方式。
  • 这取决于代码是如何编写的——例如,如果它正在搜索代码中声明的一堆字符串位,你最终还是会得到一堆内部字符串。但是重要的性能问题是当你知道你不需要它们时在堆上抛出大量不必要的中间字符串 - 单个 char 数组在内存方面总是会便宜得多,如果你编写类似于正则表达式的内部结构,更便宜的 CPU。在 .Net 中使用更少的内存意味着更少的 GC,这还有另一个 CPU 优势。
  • @chris 确实,但这依赖于一个可能不会调整大小的数组,例如问题中的语言代码。但是,就问题本身而言,我实际上建议绕过所有这些并使用枚举值。
  • 阅读stackoverflow.com/questions/3669199/…的帖子后,我很困惑链接是如何在内部存储的
【解决方案2】:

简答:使用字符串

长答案:

private string languageCode;

AFAIK 字符串存储为以长度为前缀的字符数组。在堆上实例化一个 String 对象来维护这个原始数组。但 String 对象不仅仅是一个简单的数组,它支持基本的字符串操作,如比较、连接、子字符串提取、搜索等

虽然

private char[] languageCode;

将存储为字符数组,即将在堆上创建一个数组对象,然后将其用于管理您的字符。但它仍然有一个长度属性,该属性存储在内部,因此与字符串相比没有明显的内存节省。虽然大概 Array 比 String 更简单,并且可能具有更少的内部变量,因此提供了更低的内存占用(这需要验证)。

但是OTOH你失去了对这个char数组执行字符串操作的能力。即使是字符串比较之类的操作现在也变得很麻烦。所以长话短说使用字符串!

【讨论】:

    【解决方案3】:

    这 2 个如何存储在内存中?分配值时将分配多少字节或位?

    .NET 中的每个 instance 存储如下:一个IntPtr-sized 字段用于类型标识符;另一个用于锁定实例;余数是四舍五入到IntPtr 大小的实例字段数据。因此,在 32 位平台上,每个 instance 占用 8 个字节 + 字段数据。

    这适用于stringchar[]。这两者也将数据的长度存储为一个 IntPtr 大小的整数,然后是实际数据。因此,两个字符的string 和一个两个字符的char[],在 32 位平台上,将占用 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 最终将使用 8 个字节(在 32 位机器上)每个实例加上 4 个字节用于字符。

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

    这将使用 8 个字节用于 MyClass 开销,加上 4 个字节用于 charsreference,再加上 12 个字节用于数组的开销,加上数组中每个 CharStruct 的 4 个字节。

    【讨论】:

    【解决方案4】:

    如果您想准确存储 2 个字符,并且最有效地做到这一点,请使用结构:

    struct Char2
    {
     public char C1, C2;
    }
    

    使用此结构通常不会导致新的堆分配。它只会扩大现有对象的大小(尽可能少)或消耗非常便宜的堆栈空间。

    【讨论】:

    • 堆分配将完全取决于您声明结构的where。如果在方法/属性中声明,它只会在堆栈上。在类内部,它将与其他类成员一起在堆中。
    • 它不会导致 new 分配。它只会扩大现有对象的大小(尽可能少)。
    • 是的,但堆分配通常非常快,最初不应该担心。也就是说,struct LanguageCode 的结构是一个不错的选择。
    • 那他为什么要问性能呢?也许他需要存储十亿个这样的字符串。这是一个完美的问题,我这样回答。
    • 他不是在问性能,这个词没有出现一次。他在问它们是如何存储的,哪个是首选的,你只是间接地回答后者。
    【解决方案5】:

    字符串确实有一个指针长度的大小开销,即 4 字节用于 32 位进程,8 字节用于 64 位进程。但话又说回来,字符串提供的回报比 char 数组要多得多。

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

    【讨论】:

      【解决方案6】:

      String 只是在内部实现了一个 char 类型的索引器,我们可以说 stringchar[] 类型相同,并带有许多额外的代码以使其对您有用,因此,就像一个数组一样,它存储在总是堆。

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

      字符串实现IEnumerable<char>

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

      【讨论】:

        猜你喜欢
        • 2011-09-29
        • 2011-09-07
        • 1970-01-01
        • 2016-01-12
        • 1970-01-01
        • 2014-03-08
        • 2012-11-09
        • 2012-04-28
        • 1970-01-01
        相关资源
        最近更新 更多