我想问一个关于在Java中避免String重复的问题。
上下文是:带有标记和属性的XML,如下所示:
<product id="PROD" name="My Product"...></product>
使用JibX,这个XML在这样的类中被编组/解组:
public class Product{
private String id;
private String name;
// constructor, getters, setters, methods and so on
}
该程序是一个长时间的批处理,因此创建,使用,复制了产品对象
嗯,问题是: 当我用 Eclipse内存分析器(MAT)这样的软件分析执行时,我发现了几个重复的字符串。例如,在id属性中, PROD 值在2000个实例等处重复。
我该如何避免这种情况? Product类中的其他属性可能会在执行过程中更改其值,但 id , name ...等attrs不会频繁更改。
我已经了解了一些关于 String.intern()方法的内容,但我还没有使用过,我不确定它是否是一个解决方案。我可以在这些属性中定义最常用的值,例如类中的静态最终常量吗?
我希望我能以正确的方式表达我的问题。 非常感谢任何帮助或建议。提前谢谢。
答案 0 :(得分:12)
实习将是正确的解决方案。 Java在内部池中存储字符串文字和许多其他字符串,每当即将创建新字符串时,JVM首先检查字符串是否已存在于池中。如果是,它将不会创建新实例,而是将引用传递给 interned String对象。
有两种方法可以控制此行为:
String interned = String.intern(aString); // returns a reference to an interned String
String notInterned = new String(aString); // creates a new String instance (guaranteed)
所以可能是,库实际上为所有xml属性值创建了新实例。这是可能的,您将无法更改它。
实习生具有全局效果。一个实习的字符串立即可用于“任何对象”(这个视图确实没有意义,但它可能有助于理解它。)
所以,我们假设我们在课程Foo
中有一行,方法foolish
:
String s = "ABCD";
字符串文字立即被实习。 JVM检查,如果“ABCD”已经在池中,如果没有,“ABCD”存储在池中。 JVM将对实习字符串的引用分配给s
。
现在,可能在另一个班级Bar
中,方法barbar
:
String t = "AB"+"CD";
然后JVM将像上面一样实习“AB”和“CD”,创建连接的String,看看,如果它已经是intered,嘿,是的,并且将对interned String“ABCD”的引用分配给{ {1}}。
调用t
可能有效或失败。是的,将实习字符串"PROD".intern()
。但是有一个机会,jibx确实用
"PROD"
在这种情况下, value 将不会引用一个interned String(即使String value = new String(getAttributeValue(attribute));
在池中),而是对堆上的新String实例的引用。 / p>
而且,对于命令中的另一个问题:这只发生在运行时。编译只是创建类文件,String池是对象堆上的数据结构,由JVM使用,用于执行应用程序。
答案 1 :(得分:6)
尽管String.intern()
可以通过将每个值减少到单个唯一String
实例来解决该问题,但它会引入另一个问题:每个intern()
- 编辑String
都可以存活在JVM中很长一段时间。如果ID变化很大(即它们不是有限集合的一部分,但可以是任何值),那么从长远来看,这可能会产生巨大的负面影响。
编辑:我曾经声称intern()
- ed字符串不能被GC,但是@nanda证明我错了this JavaWorld article。虽然这在一定程度上减少了intern()
引入的问题,但它仍未完全删除:intern()
提供的池无法控制,并且可能在垃圾收集方面产生意外结果。
幸运的是Guava以Interner
接口的形式提供了一个解决方案,它是帮助类Interners
:使用Interners.newStrongInterner()
可以创建一个可以充当“{3}}的对象独立String
个对象的池与String.intern()
的对象大致相同,只是池绑定到该实例,如果丢弃池,则内容也可以符合垃圾回收的条件
答案 2 :(得分:1)
是的,实习是正确的解决方案,你已完成了你的功课(即通过探查器检查这是问题所在)。
如果存储太多,实习可能会导致问题。需要增加permgen记忆。尽管有些人说过,实际的字符串也是垃圾收集的,所以如果不再使用某些字符串,那么它将成为垃圾收集对象。
一些支持文章:
答案 3 :(得分:0)
另一种解决方案:
您可以尝试在<xs:enumeration/>
属性上定义@id
限制(如果您的域模型允许这样的话)。如果JibX与JAXB或其他XML-Java映射标准一样聪明,那么这可以映射为具有常量文字的Java enum
,可以大量重用。
我会尝试ID
值,因为它看起来像是对我的枚举......
答案 4 :(得分:0)
众所周知,String对象可以通过两种方式创建,使用文字和new运算符。
如果您使用像String test = "Sample";
这样的文字,那么这将在String对象池中缓存。因此,此处不需要实习,因为默认情况下将缓存字符串对象。
但是如果你创建一个字符串对象,比如String test = new String(“Sample”);那么这个字符串对象将不会被添加到字符串池中。所以这里我们需要使用String test = new String("Sample").intern();
强制将字符串对象推送到字符串缓存。
因此,建议使用字符串文字而不是新操作符。
所以在你的情况下私有静态最终String id =“PROD”;是正确的解决方案。