6

我的程序目前存在内存问题,在检查应用程序后,我们发现该String.split()方法使用了大量内存。我尝试过使用 a StreamTokenizer,但这似乎使事情变得更加复杂。

有没有更好的方法将 longStrings分成Strings比该String.split()方法使用更少内存的 small ?

4

4 回答 4

1

任何实际使用 split 都不太可能“消耗大量内存”。您的输入必须很大(很多很多兆字节),并且您的结果分成数百万个部分才能被注意到。

下面是一些代码,它创建了大约 180 万个字符的随机字符串,并将其拆分为超过 100 万个字符串,并输出使用的内存和花费的时间。

如您所见,它并不多:仅 350 毫秒就消耗了 61Mb。

public static void main(String[] args) throws Exception {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < 99999; i++) {
        sb.append(Math.random());
    }
    long begin = System.currentTimeMillis();
    String string = sb.toString();
    sb = null;
    System.gc();
    long startFreeMem = Runtime.getRuntime().freeMemory();
    String[] strings = string.split("(?=[0-5])");
    long endFreeMem = Runtime.getRuntime().freeMemory();
    long execution = System.currentTimeMillis() - begin;

    System.out.println("input length = " + string.length() + "\nnumber of strings after split = " + strings.length + "\nmemory consumed due to split = "
            + (startFreeMem - endFreeMem) + "\nexecution time = " + execution + "ms");
}

输出(在相当典型的 windows 盒子上运行):

input length = 1827035
number of strings after split = 1072788
memory consumed due to split = 71740240
execution time = 351ms

有趣的是,没有 System.gc()使用的内存大约是 1/3:

memory consumed due to split = 29582328
于 2012-08-09T13:21:29.390 回答
0

如果您只想使用一个或几个长字符串数组,则可以拆分方面内存。长字符串将始终在内存中。喜欢

private static List<String> headlist = new ArrayList<String>();

String longstring = ".....";
headlist.add(longstring.split(" ")[0]);

比长字符串总是在内存中。JVM 不能 gc 它。

在这种情况下,我想也许你可以试试

private static List<String> headlist = new ArrayList<String>();

String longstring = ".....";
headlist.add(new String(longstring.split(" ")[0]));

如下代码

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class SplitTest {
    static Random rand = new Random();
    static List<String> head = new ArrayList<String>();

    /**
     * @param args
     */
    public static void main(String[] args) {
        while(true) {
            String a = constructLongString();
            head.add(a.split(" ")[0]); //1
            //head.add(new String(a.split(" ")[0])); //2
            if (i % 1000 == 0)
                System.out.println("" + i);
            System.gc();
        }
    }

    private static String constructLongString() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 10; i++) {
            sb.append(rand.nextInt(10));
        }
        sb.append(" ");
        for (int i = 0; i < 4096; i++) {
            sb.append(rand.nextInt(10));
        }
        return sb.toString();
    }
}

如果您使用 -Xmx60M 运行,它将内存不足大约 6000+,如果您使用代码行 2,注释第 1 行,那么它运行的时间超过 6000

于 2012-08-09T13:08:28.543 回答
0

Split 不会创建全新的字符串,它在substring内部使用它创建一个新String对象,该对象指向原始字符串的正确子字符串,而不复制底层的char[].

因此,除了对象创建的(轻微)开销之外,从内存的角度来看,它不应该产生巨大的影响。

ps:StringTokenizer使用相同的技术,因此它可能会产生与拆分相同的结果。

编辑

要查看是否是这种情况,您可以使用下面的示例代码。它拆分abc,defabc打印原始字符串和拆分字符串def的底层char[]- 输出显示它们都是相同的。

输出:

Reference: [C@3590ed52  Content: [a, b, c, ,, d, e, f]
Reference: [C@3590ed52  Content: [a, b, c, ,, d, e, f]
Reference: [C@3590ed52  Content: [a, b, c, ,, d, e, f]

代码:

public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
    String s = "abc,def";
    String[] ss = s.split(",");
    Field f = String.class.getDeclaredField("value");
    f.setAccessible(true);
    System.out.println("Reference: " + f.get(s) + "\tContent: " + Arrays.toString((char[])f.get(s)));
    System.out.println("Reference: " + f.get(ss[0]) + "\tContent: " + Arrays.toString((char[])f.get(ss[0])));
    System.out.println("Reference: " + f.get(ss[1]) + "\tContent: " + Arrays.toString((char[])f.get(ss[1])));
}
于 2012-08-09T12:54:09.003 回答
0

您需要使用某种流阅读器,而不是使用大数据字符串滥用内存。这里有一些例子:

 public static void readString(String str) throws IOException {
        InputStream is = new ByteArrayInputStream(str.getBytes("UTF-8"));

        char[] buf = new char[2048];
        Reader r = new InputStreamReader(is, "UTF-8");

        while (true) {
            int n = r.read(buf);
            if (n < 0)
                break;

            /*
             StringBuilder s = new StringBuilder();
             s.append(buf, 0, n);
             ... now you can parse the StringBuilder ...  
            */
        }
    }
于 2012-08-09T13:25:17.377 回答