Java的String内存池的实现是否遵循享元模式?
我对此有疑问是因为在Intern中没有外在状态。在GoF中,我读到应该在内在和外在状态之间保持适当的平衡。但在intern中,一切都是内在的。
或者我们可以说并没有关于属性的严格规定,只需共享对象以减少内存即可称之为享元。
请帮助我理解。
Java的String内存池的实现是否遵循享元模式?
我对此有疑问是因为在Intern中没有外在状态。在GoF中,我读到应该在内在和外在状态之间保持适当的平衡。但在intern中,一切都是内在的。
或者我们可以说并没有关于属性的严格规定,只需共享对象以减少内存即可称之为享元。
请帮助我理解。
char[]
是完全内在的,而对象所代表的字符串则是完全外在的。使用 String,你甚至不知道 char[]
的存在。 - Marko Topolnikchar[]
(或者可能是byte[]
),而不带有偏移和长度字段。同时,将char[]
作为单独的分配应该也被消除。 - Tom Hawtin - tacklineString(String)
构造函数已更改为不再复制数组。在更改之前,使用此构造函数是一种未记录的技巧,用于构造不共享数组的字符串,以解决小(子)字符串引用大数组的问题。由于不再需要这个技巧,您可以再次使用相同的数组构造字符串,尽管这些字符串将是完全相等的字符串,而不是子字符串。此外,最近的 JVM 中还有字符串去重功能。 - Holger是的,String.intern()
的实现遵循享元模式。
正如javadoc所述:
返回字符串对象的规范表示。类String私有地维护一个最初为空的字符串池。
当调用intern方法时,如果池已包含一个由equals(Object)方法确定等于此String对象的字符串,则返回池中的字符串。否则,将此String对象添加到池中,并返回对此String对象的引用。
由此可知,对于任何两个字符串s和t,当且仅当s.equals(t)为true时,s.intern()==t.intern()也为true。
所有字面值字符串和字符串常量表达式都会被内部化。字符串字面值的定义详见Java语言规范§3.10.5。
内部化的字符串驻留在 “Perm Gen” 空间中,并且对于使用 .intern()
返回的字符串对象,可以使用运算符==
,因为对于相等的值,.intern()
总是返回同一个对象。
然后请记住,.intern()
方法不会导致泄漏,因为JVM现在能够垃圾回收池。
也请尝试阅读这篇文章。
不,共享对象以减少内存并不足以称之为享元模式。换句话说,缓存并不自动成为享元模式。
我认为可以公正地说,享元模式是一种特殊形式的缓存,即部分缓存;但请注意,《设计模式》这本书在享元章节中没有使用“cache”或“caching”这些词语(尽管这些术语在前一章节中的门面模式和后续章节中的代理模式中都有使用)。
这个主题中的一些评论值得重复,因为它们简明扼要地回答了整体问题。
如果你的对象没有外在的上下文,那就只是做缓存而已。享元模式有用的原因在于人们经常忘记他们可以至少缓存独立于上下文的对象部分并共享它。
--C S
享元是关于共享对象内部的。国际化仅仅是缓存整个对象。
--Marko Topolnik
但让我们将字符串国际化与GoF定义的标准进行比较(第197页)。
当满足以下所有条件时,应用享元模式:4/5的通过标准已经相当不错了,对吧?这难道不足以说明实习/缓存和享元是相同的吗?不是的:相似并不等于相同。GoF引用中“所有”一词的强调是他们的,而不是我的。自然而然地,人们希望尽可能多地使用GoF模式名称来标记实现,因为这样做可以使这些实现合法化。(最严重的情况是工厂模式,你可以轻松地找到将每种创造性代码都标记为工厂模式的例子;但我跑题了。)如果模式没有按照其公布的定义进行保持,则它们会重叠并失去意义,从而击败它们的大部分目的(共同词汇)。
最后,让我们分析一下享元章节的第一句话:GoF如何定义享元模式的“意图”。
使用共享来有效地支持大量细粒度对象。
我认为没有外在状态的对象不是细粒度的,而恰恰相反;因此,这里建议缓存的“意图”:使用缓存来有效地支持大量粗粒度对象。
显然,字符串内部化/缓存和享元模式之间存在相似之处;但它们并不相同。
Flyweight 是关于共享对象不可变的内部状态。而 Interning 只是缓存整个对象。