C#: 相同内容的字符串

18

我听说和读过字符串是不能改变的(不可变的?)。我猜这应该是正确的。但我也听说,两个具有相同内容的字符串共享相同的内存空间(或者你可以称其为什么)。这是正确的吗?

如果是这样,那是否意味着,如果我创建一个包含数千个字符串的列表,如果其中大部分字符串相互相等,那它不会真正占用太多空间吗?

4个回答

33

编辑:在下面的答案中,我提到实习池是AppDomain特定的;我很确定这是我以前观察到的,但 String.Intern 的MSDN文档表明整个进程只有一个内部池,使得这一点更加重要。

原始答案

正如其他人所解释的那样,字符串国际化适用于所有字符串文字,但不适用于“动态创建”的字符串(例如从数据库或文件中读取的字符串,或使用StringBuilderString.Format 构建的字符串)。

然而,我不建议调用String.Intern 来解决后一个问题:它将填充intern池为您的AppDomain 的生命周期。相反,请使用仅限于您使用的本地池。以下是这样一个池的示例:

public class StringPool
{
    private readonly Dictionary<string,string> contents =
        new Dictionary<string,string>();

    public string Add(string item)
    {
        string ret;
        if (!contents.TryGetValue(item, out ret))
        {
            contents[item] = item;
            ret = item;
        }
        return ret;
    }
}

你可以使用类似以下的代码:

string data = pool.Add(ReadItemFromDatabase());

(请注意,该池不是线程安全的;正常使用不需要它是线程安全的。)
这样,您可以在不再需要池时立即丢弃它,而不是永远占用内存中可能很大的字符串数量。如果您真的想要,还可以使其更智能化,实现LRU缓存或其他什么东西。
编辑:仅澄清为什么这比使用String.Intern好...假设您从数据库或日志文件中读取了一堆字符串,对它们进行处理,然后转移到另一个任务。如果您在这些字符串上调用String.Intern,则只要您的AppDomain存在(甚至可能连那都不行),它们就永远不会被垃圾回收。如果您加载了几个不同的日志文件,您将逐渐在intern池中累积字符串,直到完成或耗尽内存。相反,我建议采用以下模式:
void ProcessLogFile(string file)
{
    StringPool pool = new StringPool();
    // Process the log file using strings in the pool
} // The pool can now be garbage collected

在这里,您可以获得同一文件中多个字符串仅存在于内存中一次的好处(或者至少只会经过gen0一次),但是您不会污染“全局”资源(即intern池)。


2
哦,所以interned字符串会永久存在?这不太好,呵呵。谢谢你指出来。 - Svish
你使用TryGetValue而不是ContainsKey有什么特别的原因吗? - C.B.
3
为什么要通过 ContainsKey 两次查找然后使用索引器来获取值,当可以通过 TryGetValue 一次完成两个操作呢?@C.B.:是的——我需要这个值。 - Jon Skeet
首先,为字符串对象分配的内存不太可能在公共语言运行时(CLR)终止之前被释放。原因是CLR对字符串对象的引用可能会在应用程序甚至应用程序域终止后持久存在。 - Paul Zahra
@PaulZahra:很有趣,尽管“可以持续”并不等同于“一定会持续”。我会稍微修改我的答案。 - Jon Skeet
显示剩余4条评论

6

在代码中创建?难道不是所有的字符串都是在代码中创建的吗?或者你是指硬编码的字符串,与从数据库运行时获取的字符串相对吗? - Svish
在代码中创建的字符串不会自动进行内部化,但可以使用String.Intern()方法进行内部化。请注意,在不同版本的.NET中处理内部化空字符串时存在一些差异(错误?):http://msdn.microsoft.com/en-us/library/system.string.intern.aspx?ppud=4 - Michael Burr
那么当从数据库获取字符串时,我需要使用String.Intern才行吗? - Svish

1

如果我没记错的话,在代码中硬编码的字符串会被单独池化。这称为“Interned”,并且有一种方法可以查询字符串是否:String.IsInterned Method

在该页面的“备注”下,您可以阅读到:

公共语言运行时会自动维护一个表,称为“intern pool”,其中包含程序中声明的每个唯一文字常量的单个实例,以及您通过编程方式添加的任何唯一 String 实例。

希望这能对你有所帮助,如果我有错误,请指出。

马蒂亚斯


0
为了使字符串“共享”它们的内存位置,可以将它们放入intern池中。该池包含对程序中声明或通过编程创建的每个唯一字面字符串的单个引用。
请注意,代码中的所有字符串字面量都会自动进入intern池。

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