我有一个 android 应用程序,它从 telnet 客户端获取巨大的字符串对象。后来我只使用了大字符串的一小部分。我用的是
新字符串(旧字符串的一部分);
将新字符串 Char 数组与旧 Strings char 数组分开。因此旧字符串应该被垃圾收集,但令人惊讶的是新字符串仍然具有对旧对象的引用。 我可以用“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;
}
方法输入也是更大字符串的一部分。
最佳答案
每个 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[]
的副本,而是直接引用它。您不能直接调用此构造函数,因为它是包私有(private)的,因此它仅由 substring 等方法在内部使用:
/*
* 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
关于java - Android 上的字符串复制,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14646545/