0

我有 android 应用程序,它从 telnet 客户端获取巨大的字符串对象。后来我只使用大字符串的一小部分。我用

new String(Part of old string);

将新字符串字符数组与旧字符串字符数组分开。所以旧的字符串应该被垃圾收集,但令人惊讶的是新字符串仍然有对旧对象的引用。我可以用“Eclipse Memory Analyzer”看到它。这很快就会溢出我的 16Meg 应用程序内存。

如何避免这种情况?

    private WifiChannel parse1(String channLine){
    //scanning with "iwlist wlan0 scanning" the getChans1 method
    String[] input = channLine.split(System.getProperty("line.separator"));
    if (input.length < 4);
    String segment[];
    String segment2[];
    WifiChannel chan = new WifiChannel();
    try {
        if (input.length > 5){
            chan.setMacAddress(new String(input[0]));
            segment = input[1].split(":");
            chan.setChannel(Integer.parseInt(segment[1].trim()));
            segment = input[3].split(" ");
            segment2 = segment[20].split("=");
            chan.setQuality(new String(segment2[1]));
            segment2 = segment2[1].split("/");
            chan.setSignalStrength((Integer.parseInt(segment2[0].trim())*100)/Integer.parseInt(segment2[1].trim())+"%");
            segment2 = segment[23].split("=");
            try{chan.setSignalLevel(Integer.parseInt(segment2[1].trim()));}catch(Exception e){chan.setSignalLevel(0);}
            segment = input[5].split(":");
            chan.setName(new String(segment[1].replaceAll("^\"|\"$", "")));
            for (int i = 6;i<input.length;i++)
                if (input[i].contains("Mode"))
                    segment = input[i].split(":");
            chan.setChannelMode(new String(segment[1]));
            String band = "";
            if(channLine.contains("5.5 Mb/s"))band = band +"b";
            if(channLine.contains("12 Mb/s") )band = band +"g";
            chan.setBand(new String(band));
        }
    }catch (Exception e){Log.e("","",e);}
    return chan;
}

方法输入也是更大字符串的一部分。

4

1 回答 1

3

每个 String 实例都由一个 char 数组支持:

public class String {

    private final char[] value;

    ...
}

由于效率原因,对字符串的某些操作可以创建一个char[]与原始字符串共享的新字符串实例。这是可能的,因为所有字符串都是不可变的。这方面的一个例子是substring()方法:

public String substring(int start) {
    if (start == 0) {
        return this;
    }
    if (start >= 0 && start <= count) {
        return new String(offset + start, count - start, value); // !!!
    }
    throw indexAndLength(start);
}

添加了我的评论的那一行调用了一个构造函数,它不创建 的副本char[],而是直接引用它。您不能直接调用此构造函数,因为它是包私有的,因此它仅在子字符串等方法内部使用:

/*
 * Internal version of the String(char[], int, int) constructor.
 * Does not range check, null check, or copy the character array.
 */
String(int offset, int charCount, char[] chars) {
    this.value = chars;
    this.offset = offset;
    this.count = charCount;
}

现在,如果您从一个非常长的字符串创建一个短子字符串,那么这个短子字符串仍然引用原始的大char[]. 即使在原始字符串被垃圾回收之后,它的大数组仍然保留在内存中,即使现在只有一小部分可以被新的子字符串访问。这有效地造成了内存泄漏

为了解决这个问题,通常的技术是使用复制构造函数从子字符串创建一个新字符串,它只复制原始所需范围的副本char[]

String longString = "1234567890";
// backed by char[] of length 10

String substring = longString.substring(5);
// value is "67890", but still backed by the original char[] of length 10

String copy = new String(substring);
// also has value "67890", but now backed only by char[] of length 5

编辑:

为了完整起见,这是复制构造函数的来源。如您所见,如果原始字符串引用了与字符串本身长度相同的数组,则不需要复制该数组,因为其中没有“死字符”。但是,如果数组大于字符串的长度,则执行数组“活动”范围的副本。

public String(String toCopy) {
    value = (toCopy.value.length == toCopy.count)
        ? toCopy.value
        : Arrays.copyOfRange(toCopy.value, toCopy.offset,
            toCopy.offset + toCopy.length());
    offset = 0;
    count = value.length;
}

注: 以上所有源代码均来自Android API 15

于 2013-02-01T14:33:16.007 回答