使用StringBuffer,StringBuilder,String.intern()优化String的Java堆使用

时间:2014-10-10 00:22:26

标签: java string optimization heap-memory visualvm

我正在使用VisualVM监视大型Java应用程序的性能和CPU。 当我查看其内存配置文件时,我发现char数组正在使用最大堆(大约50%)。

以下是内存配置文件的屏幕截图:

enter image description here

在任何给定时间的内存配置文件中,我都会看到大约9000个char []对象。

应用程序接受一个大文件作为输入。该文件大致有大约80行,每行包含15-20个分隔的配置选项。应用程序解析文件并将这些行存储在字符串的ArrayList中。然后它解析这些字符串以获取每个服务器的各个配置选项。

应用程序还经常将每个事件记录到控制台。

Strings的Java实现在内部使用char []以及对数组的引用和3整数

从互联网上的不同帖子看,StringBuffer,StringBuilder,String.intern()似乎是内存效率更高的数据类型。

他们如何与java.lang.String进行比较?有没有人对它们进行基准测试?如果应用程序使用多线程(它确实如此),它们是安全的替代方案吗?

1 个答案:

答案 0 :(得分:1)

我所做的是拥有一个或多个字符串池。我这样做是为了a)如果我在池中有一个字符串而没有创建新的字符串,并且b)减少保留的内存大小,有时减少3-5倍。您可以自己编写一个简单的字符串,但我建议您先考虑如何读取数据以确定最佳解决方案。这很重要,因为如果你没有有效的解决方案,你可能会更加容易。

正如EJP指出一次处理一行更有效,就像在阅读时解析每一行一样。即intdouble占用的空间远远小于相同的字符串(除非您的复制率非常高)


这是一个StringInterner的示例,它使StringBuilder避免不必要地创建对象。首先使用文本填充循环使用的StringBuilder,如果匹配该文本的String在interner中,则返回该String(或StringBuilder的toString()。)好处是您只创建对象(并且不超过当你看到一个新的String(或至少一个不在数组中)时,这可以获得80%到99%的命中率,并在加载许多数据串时显着减少内存消耗(和垃圾)。

public class StringInterner {
    @NotNull
    private final String[] interner;
    private final int mask;

    public StringInterner(int capacity) {
        int n = nextPower2(capacity, 128);
        interner = new String[n];
        mask = n - 1;
    }

    @Override
    @NotNull
    public String intern(@NotNull CharSequence cs) {
        long hash = 0;
        for (int i = 0; i < cs.length(); i++)
            hash = 57 * hash + cs.charAt(i);
        int h = hash(hash) & mask;
        String s = interner[h];
        if (isEqual(s, cs))
            return s;
        String s2 = cs.toString();
        return interner[h] = s2;
    }

    static boolean isEqual(@Nullable CharSequence s, @NotNull CharSequence cs) {
        if (s == null) return false;
        if (s.length() != cs.length()) return false;
        for (int i = 0; i < cs.length(); i++)
            if (s.charAt(i) != cs.charAt(i))
                return false;
        return true;
    }

    static int nextPower2(int n, int min) {
        if (n < min) return min;
        if ((n & (n - 1)) == 0) return n;
        int i = min;
        while (i < n) {
            i *= 2;
            if (i <= 0) return 1 << 30;
        }
        return i;
    }

    static int hash(long n) {
        n ^= (n >> 43) ^ (n >> 21);
        n ^= (n >> 15) ^ (n >> 7);
        return (int) n;
    }
}

这个类很有意思,因为它在传统意义上不是线程安全的,但在并发使用时会正常工作,实际上当多个线程拥有不同的数组内容视图时可能会更有效。