2013-02-01 47 views
0

我有android應用程序,它從telnet客戶端獲取巨大的String對象。稍後我只使用大字符串的一小部分。我用Android上的字符串複製

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; 
} 

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

+0

嘗試使用'String.substring()'方法。 – Thommy

+0

你能告訴我們所有的代碼嗎?你什麼時候檢查內存使用情況?不保證舊的字符串會立即清除。如果您不知道,只要您重新分配舊字符串被破壞的字符串(在下一次垃圾回收迭代時),字符串就是不可改變的。 – Grambot

+0

不能你做oldString = null; ? –

回答

3

每個字符串實例由一個字符數組支持:

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

+0

爲什麼這可能不適用於android? – Balvonas

+0

我不確定。 String.split似乎根據source [here]使用了substring()方法(https://android.googlesource.com/platform/libcore/+/refs/heads/master/luni/src/main/java /java/lang/String.java)和[here](https://android.googlesource.com/platform/libcore/+/refs/heads/master/luni/src/main/java/java/util/regex/ Splitter.java),但是你大量使用複製構造函數應該可以消除這種情況。 「Eclipse Memory Analyzer」可以顯示代碼中的哪些特定對象受此影響? – Natix