Java中的flyweight字符串何时有用?
我理解java的字符串实习的基本思想 ,但我想弄清楚它发生在哪些情况,以及我需要做哪些自己的轻量级。
有点相关:
- Java字符串:“String s = new String(”傻“);”
- Java中String flyweight实现的最佳替代方法从未得到过回答
他们一起告诉我, String s = "foo"
是好的, String s = new String("foo")
很糟糕,但没有提到任何其他情况。
特别是,如果我解析一个有很多重复值的文件(比如一个csv),Java的字符串实习会覆盖我还是我需要自己做一些事情? 关于字符串实习是否适用于我的其他问题 ,我得到了相互矛盾的建议
完整的答案有几个片段,所以我在这里总结一下:
默认情况下,java仅实现编译时已知的字符串。 String.intern(String)
可以在运行时使用,但它执行得不是很好,所以它只适用于你确定会重复很多次数较少的String
。 对于较大的Strings系列,它是拯救的番石榴(参见ColinD的答案)。
不要在代码中使用String.intern()。 如果你可能得到20个或更多不同的字符串,至少不会。 根据我使用String.intern
经验,当你有几百万个字符串时,会减慢整个应用程序的速度。
要避免重复的String
对象,只需使用HashMap
。
private final Map pool = new HashMap(); private void interned(String s) { String interned = pool.get(s); if (interned != null) { return interned; pool.put(s, s); return s; } private void readFile(CsvFile csvFile) { for (List row : csvFile) { for (int i = 0; i < row.size(); i++) { row.set(i, interned(row.get(i))); // further process the row } } pool.clear(); // allow the garbage collector to clean up }
使用该代码,您可以避免一个CSV文件的重复字符串。 如果你需要在更大的范围内避免它们,请在另一个地方调用pool.clear()
。
Guava为您提供的一个选项是使用Interner而不是使用String.intern()
。 与String.intern()
不同,Guava Interner
使用堆而不是永久生成。 此外,您可以选择使用弱引用来实现String
,这样当您完成使用这些String
, Interner
将不会阻止它们被垃圾回收。 但是,当您使用Interner
时,在完成字符串时将其丢弃,您可以使用Interners.newStrongInterner()
强引用来代替可能更好的性能。
Interner interner = Interners.newWeakInterner(); String a = interner.intern(getStringFromCsv()); String b = interner.intern(getStringFromCsv()); // if a.equals(b), a == b will be true
此信息可能已过期,我不再需要备份代码……
(什么不是过时的):
通过扫描仪,读卡器等读取字符串…不会被实习。 只有字符串文字是实习的(当然这取决于实施,我认为没有任何东西说它们不能被实习)。
(可能是过时的):
我写了一个程序,我想要快速,并尽可能少使用内存。 每次从文件中读取一个String时,我都会尝试使用和不使用实习生。 实习生的方式明显长于不使用实习生,以至于我决定不做实习生。 如果性能问题,请尝试使用/不使用实习生来安排代码。 您可能还想检查内存使用情况(一个分析器对此有用),有/无实习生,看看权衡是否会对您产生影响。
读取String javadoc
所有文字字符串和字符串值常量表达式都是实体。
这使我相信,在编译程序之后,从文件中获取的字符串将不会自动实现。
如果你说的话,
String x = "string";
这将由编译器实现,因为它在编译时可见。
如果您知道输入文件中某些字符串非常常见,则可以调用
stringFromFile.intern();
并且该特定字符串将添加到实习池中供以后使用。 您甚至可以通过在代码的主要部分或静态部分中调用实习生来预缓存它们。
您可以尝试对特定输入进行实验,看看如果手动实习某些数据并将其与默认的非实习生行为进行比较,最佳情况会发生什么。
据我所知,字符串实习仅针对字符串文字自动发生,所有其他必须使用{@link java.lang.String #intern()}方法以编程方式实现。 因此,使用已经实现的String字符串通过其构造函数构造String会生成一个新的String,该String不会被实现,但包含与构造它的实习文字相同的内容。
我在javatechniques.com上找到了实习的基本概述(可能有点基础,但仍然可以解释得很好)。
在大多数情况下,string是从byte
或char
数组创建的(除非它是代码中的字符串文字),因此您可以测试它。
String s = "test"; String s1 = new String(s.getBytes()); String s2 = String.valueOf(s.toCharArray()); String s3 = new String(s.toCharArray()); System.out.println(s == s1); System.out.println(s == s2); System.out.println(s == s3);
为所有人打印false
。 但你可以明确地实习字符串,如果你有东西你会有很多重复的值。 如果你将它添加到上面的例子中,它将为所有三个比较打印为true
s1 = s1.intern(); s2 = s2.intern(); s3 = s3.intern();
请参阅API中的String #intern描述 。
编辑
那么在读取的每个值上使用intern()是否是实现flyweighting的合理方法?
是的,假设没有旧字符串的引用。 如果旧的字符串引用不再在任何地方使用,它将被垃圾收集。
什么时候实习一个字符串? 当你知道你将在给定的地方有很多带有低基数的字符串时。
例如……批处理代码。 您计划处理1亿行,创建的许多POJO都有一个字段(比如人物对象上的CITY字段),这只是一些可能的答案之一(纽约,芝加哥等)。 做ENUM的选择太多,但你真的不需要创建4500万字符串来表示纽约。 您可以使用实习或某种家庭滚动变体(弱参考映射可能比String.intern更好)来减少您的内存占用。
您可以以可能的CPU工作为代价来节省内存空间……在某些地方可能值得,但很难说。 GC非常快,你的重复字符串一旦完成就会得到GC。
因此,如果你进入一个你正在进入记忆墙的地方,并且拥有低基数的弦乐……你可以考虑实习。
我想,在引入-XX:StringTableSize
开关之后, String.intern()
应该是可用的。 速度很快的原因是表格具有固定的大小,并且即使没有任何实习也无法通过字符串值常量重载。
表格大小应该是素数!
使用更大的表应该使String.intern()
几乎与任何其他哈希表一样快。 不完全是因为使用modulo而不是按位和。 从积极的方面来说,内存开销要低得多(不需要Map.Entry
和WeakReference
)。