【问题标题】:Is Java's String Intern a flyweight?Java 的 String Intern 是轻量级的吗?
【发布时间】:2012-06-26 16:44:58
【问题描述】:

Java的String内存池的实现是否遵循flyweight模式?

我有这个疑问的原因是,我看到实习生中不涉及外在状态。在 GoF 中,我读到应该在内在状态和外在状态之间取得适当的平衡。但在实习生中,一切都是内在的。

或者我们应该说没有关于属性的严格规则,仅共享对象以减少内存就足以称其为享元。

请帮助我理解。

【问题讨论】:

  • 我会说如果你的对象没有外在的 context ,那么你实际上只是在缓存。享元模式甚至有用定义的全部原因是人们经常忘记他们至少可以缓存与上下文无关的对象的一部分并共享它。

标签: java design-patterns flyweight-pattern


【解决方案1】:

您已经正确地确定了 Interning 和 Flyweight 都基于相同的想法:缓存和共享公共状态。

使用享元,在没有外部状态可存储的极端情况下,只保留指向内部状态的指针。那么外部状态甚至不需要是一个对象,指针本身可以是外部状态。那时 Flyweight 已成为实习生。

实习生是否“真的”是一种享元,只是关于定义的争论。最重要的是了解如何将一个人视为另一个人的特殊实例,所以你很好。

【讨论】:

  • 我认为在这个答案中,术语 intrinsicextrinsic 是相反的。内在数据是共享的共性。外部数据是不共享的唯一上下文。
  • @jaco0646 哦!你是对的,我的答案已经错了三年。我更正了。
【解决方案2】:

不,共享对象以减少内存不足以称其为享元。换句话说,缓存不会自动成为享元模式。

我认为可以公平地说享元是一种特殊形式的缓存,即部分缓存;但请注意,GoF 书在享元章节的任何地方都没有使用“缓存”或“缓存”这两个词(尽管这些术语分别在前面和后面的章节中使用,Facade 和 proxy)。

此线程中的几个 cmets 值得重复,因为它们简洁地回答了整个问题。

  • 如果您的对象没有外部上下文,那么您只是在缓存。 Flyweight 模式甚至有用的全部原因 定义,是人们经常忘记他们至少可以缓存一部分 独立于上下文的对象并共享它。

    --C S

  • 享元是关于共享对象内部的。实习只是缓存整个对象。

    --马尔科·托波尔尼克

但让我们将 String interning 与 GoF 定义的标准(第 197 页)进行比较。

所有满足以下条件时应用享元模式:

  • 应用程序使用大量对象。
  • 由于对象数量庞大,存储成本很高。
  • 大多数对象状态都可以设置为外部状态。
  • 一旦外部状态被移除,许多对象组可能会被相对较少的共享对象所取代。
  • 应用程序不依赖于对象标识。由于享元对象可能是共享的,因此对于概念上不同的对象,身份测试将返回 true。
  1. 显然,许多应用程序使用大量字符串,因此该标准通过。
  2. 存储字符串是昂贵的,至少与原始类型相比,所以让我们通过这个标准。
  3. 这就是我们被绊倒的地方:none 字符串的状态是外在的。此标准不合格。
  4. 如果我们大方地忽略有关外部状态的部分,我们也可以通过此标准,因为字符串确实倾向于被重用。
  5. 任何曾在 Java 中使用 == 比较字符串的人都知道不依赖对象标识,因此该标准通过了。

4/5 的通过标准很不错吧?这还不足以说明实习/缓存和轻量级是相同的吗?否:相似!= 相同。 GoF 引用中对 all 一词的强调是他们的,不是我的。人们自然强烈希望用 GoF 模式名称标记尽可能多的实现,因为这样做会给这些实现带来合法性。 (最令人震惊的案例是工厂模式,你可以很容易地找到可以想象的各种创建代码的标签;但我离题了。)如果模式不符合其发布的定义,它们就会重叠并失去意义,从而击败了大部分他们的目的(常用词汇)。

最后,我们来分析flyweight章节的第一句话:GoF定义为flyweight模式的Intent

使用共享有效地支持大量细粒度对象。

我认为没有外在状态的对象不是细粒度的,而是相反;所以这里有一个缓存的建议Intent:使用缓存来有效地支持大量粗粒度的对象。

很明显,String interning/caching 和 Flyweight Pattern 有相似之处;但它们并不相同。

【讨论】:

    【解决方案3】:

    不管实习如何,Java String 通过在字符串和通过substring 和类似方法调用从它派生的字符串之间共享char[] 来利用享元模式。不过,这也有不利的一面:如果你从一个大字符串中提取一个小子字符串,那么大的 char[] 将不符合垃圾回收条件。

    注意: 从 OpenJDK 版本 1.7.0_06 开始,上述内容已过时:代码已更改,char[] 不再在实例之间共享。 substring() 创建一个新数组。

    【讨论】:

    • 在享元对象中保持内在状态并传递外在状态信息——我们需要担心这个吗?因为在 GoF 书中,我看到更重视内在/外在的分离。在 char[] flyweight 中,什么是内在和外在?
    • 这很简单——char[] 完全是内在的,而对象表示的字符串完全是外在的。使用字符串你甚至不知道char[] 存在。
    • HotSpot 实现最终更改为使用精确长度char[](或推测为byte[]),没有偏移和长度字段。确实有char[] 作为单独的分配也应该被淘汰。
    • @TomHawtin-tackline 这很有趣。你能指点我写一篇关于那个的文章吗?我对血淋淋的细节很感兴趣 :)
    • @fredoverflow 答案仍然有效;它只是值得一点阐述。随着不再与子字符串共享数组的更改,String(String) 构造函数被更改为不再复制数组。在更改之前,使用这个构造函数是一个未记录的技巧来构造一个不共享数组的字符串,以解决小(子)字符串引用大数组的问题。由于不再需要这个技巧,您可以再次使用相同的数组构造字符串,尽管这些都是相等的字符串,而不是子字符串。然后,最近的 JVM 中有 String Deduplication
    【解决方案4】:

    正如其他人所说,String.intern() 是关于缓存的。它返回对池中已存储的字符串文字的引用。通过这种方式,它在某种程度上类似于享元模式,因为它使用现有对象,从而降低内存消耗并提高性能(尽管实习生在字符串池中查找也有自己的性能开销)。因此,这两个可能看起来很相似,但实际上并非如此。

    【讨论】:

      【解决方案5】:

      享元是关于共享对象不可变内部。实习只是缓存整个对象。

      【讨论】:

        【解决方案6】:

        是的,String.intern() 实现遵循享元模式。

        正如javadoc 所说

        返回字符串对象的规范表示。一池 字符串,最初为空,由 String 类私下维护。

        当调用intern方法时,如果池中已经包含一个 字符串等于由 equals(Object) 确定的此 String 对象 方法,然后从池中返回字符串。否则,这 将字符串对象添加到池中并引用此字符串 对象被返回。

        因此对于任意两个字符串 s 和 t,s.intern() == t.intern() 当且仅当 s.equals(t) 为真时为真。

        所有文字字符串和字符串值常量表达式都是 实习。字符串文字在 Java 语言的 §3.10.5 中定义 规格

        内部化的字符串位于“Perm Gen”空间和.intern()返回的字符串对象上,您可以使用运算符==,因为.intern()总是返回相同的对象以获得相同的值。

        那么请记住 .intern() 方法不会产生泄漏,因为今天的 JVM 能够对池进行垃圾处理。

        也尝试阅读此article

        【讨论】:

        • 但是享元是关于共享对象内部的。实习只是缓存整个对象。我觉得这里不合适。
        • 我的问题是,“仅通过共享来节省内存就可以称之为轻量级吗?”与外部/内部状态等实现细节无关..
        • 也许我读的是错误的,但在维基百科上,样本也返回并缓存了整个对象 (Flyweight pattern)。也许其他人可以澄清这种模式。
        • 共享整个对象确实是最好的,但是您的设计根本没有被“享元”一词所涵盖,其中的重点是拥有不同的、复杂的实体,这些实体在幕后共享其复杂的数据.您引用的维基百科页面非常清楚。
        • 然而,如果你是对的,网络上充斥着关于享元模式的不良样本:(
        猜你喜欢
        • 2011-08-31
        • 2020-07-16
        • 2011-02-01
        • 2010-09-29
        • 2011-06-16
        • 2011-03-20
        • 2017-05-06
        • 1970-01-01
        • 2010-10-02
        相关资源
        最近更新 更多