Java中的flyweight字符串何时有用?

时间:2010-10-19 21:17:32

标签: java string design-patterns

我理解java的字符串实习的basic idea,但我正在试图弄清楚它发生在哪些情况,以及我需要做哪些自己的轻量级。

有点相关:

他们一起告诉我,String s = "foo"很好,String s = new String("foo")很糟糕,但没有提到任何其他情况。

特别是,如果我解析一个有很多重复值的文件(比如一个csv),Java的字符串实习会覆盖我还是我需要自己做一些事情?关于字符串实习是否适用于我的other question

,我得到了相互矛盾的建议

完整的答案有几个片段,所以我在这里总结一下:

默认情况下,java仅实现编译时已知的字符串。 String.intern(String)可以在运行时使用,但效果不是很好,所以它只适用于较小数量的String,你肯定会重复很多。对于较大的Strings系列,它是拯救的番石榴(参见ColinD的回答)。

8 个答案:

答案 0 :(得分:20)

一个选项Guava为您提供的是使用Interner而不是String.intern()。与String.intern()不同,Guava Interner使用堆而不是永久生成。此外,您可以选择使用弱引用来实现String s,这样当您使用这些String时,Interner将无法阻止它们被垃圾回收。但是,如果您使用Interner以便在完成字符串时将其丢弃,则可以使用Interners.newStrongInterner()的强引用代替可能更好的性能。

Interner<String> interner = Interners.newWeakInterner();
String a = interner.intern(getStringFromCsv());
String b = interner.intern(getStringFromCsv());
// if a.equals(b), a == b will be true

答案 1 :(得分:7)

不要在代码中使用String.intern()。如果你可能得到20个或更多不同的字符串,至少不会。根据我的经验,当你有几百万字符串时,使用String.intern会减慢整个应用程序。

要避免重复的String个对象,只需使用HashMap

private final Map<String, String> pool = new HashMap<String, String>();

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<String> 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()

答案 2 :(得分:2)

此信息可能已过期,我不再需要备份代码......

(什么不是过时的):

通过扫描仪,读卡器等读取字符串...不会被实习。只有字符串文字是实习的(当然这取决于实现,我认为没有任何东西说它们不能被实习)。

(可能已过时):

我编写了一个程序,我想要快速,并尽可能少地使用内存。每次从文件中读取一个String时,我都会尝试使用和不使用实习生。实习生的方式显着长于不使用实习生,以至于我决定不做实习生。如果性能问题,请尝试使用/不使用实习生来安排代码。您可能还需要检查内存使用情况(分析器对此有用),有/无实习生,看看权衡是否会对您产生影响。

答案 3 :(得分:1)

在大多数情况下,字符串是从bytechar数组创建的(除非它是代码中的字符串文字),因此您可以对其进行测试。

    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();

See String#intern description in the API

修改
那么在读取的每个值上使用intern()是否是实现flyweighting的合理方法?
是的,假设没有旧字符串的引用。如果旧的字符串引用不再在任何地方使用,它将被垃圾收集。

答案 4 :(得分:1)

阅读 String javadoc

  

所有文字字符串和字符串值常量表达式都是实现的。

这让我相信,在编译程序之后,从文件中获取的字符串将不会自动实现。

如果你说的话,

String x = "string";

将由编译器实现,因为它在编译时可见。

如果您知道某些字符串在输入文件中非常常见,则可以调用

stringFromFile.intern();

并且该特定字符串将添加到实习池中供以后使用。您甚至可以通过在代码的主要部分或静态部分中调用实习生来预缓存它们。

您可以尝试对特定输入进行实验,看看如果您手动实习某些数据并将其与默认的非实习生行为进行比较,最佳情况会发生什么。

答案 5 :(得分:1)

据我所知,字符串实习仅针对字符串文字自动发生,所有其他必须使用{@link java.lang.String #intern()}方法以编程方式实现。因此,使用已经实现的String字符串通过其构造函数构造String会生成一个新的String,该String不会被实现,但包含与构造它的实习文字相同的内容。

我在javatechniques.com上找到了实习的基本概述(可能有点基本,但仍然可以解释得很好)。

答案 6 :(得分:1)

什么时候实习一个字符串?如果你知道在给定的地方你会有很多低基数的字符串。

例如......批处理代码。您计划处理1亿行,创建的许多POJO都有一个字段(比如人物对象上的CITY字段),这只是一些可能的答案之一(纽约,芝加哥等)。做ENUM的选择太多,但你真的不需要创建4500万字符串来说纽约。您可以使用实习或某种类型的自制滚动变体(弱参考映射可能比String.intern更好)来减少您的内存占用。

您可以以可能的C​​PU工作为代价来节省内存空间......在某些地方可能值得,但很难说。 GC非常快,您的重复字符串将在使用完毕后立即获得GC。

所以,如果你进入一个你正在进入记忆墙的地方,并且有一个低基数的弦乐......你可以考虑实习。

答案 7 :(得分:1)

我想,在引入-XX:StringTableSize开关后,String.intern()应该可用。速度很快的原因是表格具有固定的大小,即使没有任何实习也无法通过字符串值常量重载。

表格大小应该是素数!

使用更大的表应该使String.intern()几乎与任何其他哈希表一样快。不完全是因为使用modulo而不是按位和。从积极的方面来看,内存开销要低得多(不需要Map.Entry也不需要WeakReference)。