for循环是OutOfMemoryError的原因吗? (蚀)
Is the for loop the reason for OutOfMemoryError? (Eclipse)
您好,我正在编写一个将字符串解析为单个组件的程序,但是当我尝试对其进行测试时,出现内存不足错误。我觉得我的 for/while 循环好像是无限的,但我似乎找不到原因。
//for loop to loop through char of string
for(int i=0; i<expressionString.length(); i++) {
//cast char into ascii int
int ascii = (int) charAt(i);
//appending to token if one of singly operator symbols: *,/,(,),[,]
if(ascii == 40 || ascii == 41 || ascii == 42 || ascii == 47 || ascii == 91 || ascii == 93){
token.append((char) ascii);
tokenList.add(token.toString());
} //append if +, -
else if(ascii == 43 || ascii == 45) {
token.append((char) ascii);
//check next char if + or /, if so append to token again
int nextChar = (char) charAt(i+1);
if(nextChar == 43 || nextChar == 45) {
token.append((char) nextChar);
}
tokenList.add(token.toString());
} //appending to token if it's a num
else if ( ascii >= 48 || ascii <=57) {
token.append((char) ascii);
//check if next char is a num
while ((int) charAt(i+1) >= 48 || (int) charAt(i+1) <= 57) {
//increment i in for loop to check
i++;
token.append((int) charAt(i));
}
tokenList.add(token.toString());
}
//
}
请让我知道这是否是我的代码错误,因为我似乎无法确定问题出在哪里。谢谢!
这是您在该循环中所做操作的简化版本。
public class Main {
public static void main(String[] args) {
String str = "ABCDE";
StringBuilder sb = new StringBuilder();
List<String> list = new ArrayList<>();
for (char c : str.toCharArray()) {
sb.append(c);
list.add(sb.toString()); // <-- Problem! This adds the *entire* contents of the StringBuilder as a new String to the list.
}
System.out.println(list);
}
}
这个程序打印
[A, AB, ABC, ABCD, ABCDE]
这是因为每次我们将 char
附加到 StringBuilder
时,我们都会将 StringBuilder
的 整个 内容添加为ArrayList
.
的新 String
现在假设我们将 "ABCDE"
替换为长度 1000000
的 String
,例如我们将第一行更改为
String str = Stream.generate(() -> "A").limit(1000000).collect(Collectors.joining()); // String of length 1000000
我们现在正尝试创建 1000000 个 String
个长度从 1
到 1000000
的对象,结果可预测。
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOfRange(Arrays.java:3664)
at java.lang.String.<init>(String.java:207)
at java.lang.StringBuilder.toString(StringBuilder.java:407)
at my_package.Main.main(Main.java:17)
如何解决?这取决于你想做什么(我们没有所有的上下文),但我怀疑你不需要 StringBuilder
和 List
.
正如我在评论中指出的那样,您附加到 StringBuilder
而没有从中删除任何内容的事实是可疑的。
StringBuilder
只是 char[]
的包装器,它会在必要时自动调整大小以容纳您尝试追加的新文本。您可以在堆栈跟踪中看到 OOM 发生在这些自动调整大小之一期间。
此问题的一个解决方案是最初分配足够大的缓冲区,然后在向 StringBuilder
添加更多文本之前不需要调整大小:
StringBuilder token = new StringBuilder(MAXIMUM_EXPECTED_SIZE);
问题在于可能难以确定MAXIMUM_EXPECTED_SIZE
;此外,大多数时候您可能会浪费大量内存,因为您几乎没有向缓冲区追加那么多文本。
似乎您实际上并不想在将文本传输到 tokenList
后将其保留在 token
中。您可以使用以下命令从缓冲区中明确删除它:
token.delete(0, token.length());
// or
token.setLength(0);
(实际上,这并没有删除数据,它只是允许后续追加覆盖它)
但这仍然是一种浪费:您根本不需要 StringBuilder
。
考虑一下你如何处理这些数字:
if ( ascii >= 48 || ascii <=57) {
token.append((char) ascii);
//check if next char is a num
while ((int) charAt(i+1) >= 48 && (int) charAt(i+1) <= 57) {
// ^^ NB
//increment i in for loop to check
i++;
token.append((int) charAt(i));
}
tokenList.add(token.toString());
}
你在这里显然想做的是附加第 i
个字符(含)和第 j
个字符(不含)之间的所有内容,其中 j
指向字符串的末尾或非数字字符。所以你可以这样做:
if ( ascii >= 48 || ascii <=57) {
int j = i + 1;
//check if next char is a num
while (j < expressionString.length() && charAt(j) >= '0' && charAt(j) <= '9') {
j++;
}
tokenList.add(expressionString.subString(i, j));
i = j;
}
您可以对其他附加标记执行类似操作。这只是删除了 StringBuilder
的 "middle man",这显然避免了重新分配其内部缓冲区的问题。
您好,我正在编写一个将字符串解析为单个组件的程序,但是当我尝试对其进行测试时,出现内存不足错误。我觉得我的 for/while 循环好像是无限的,但我似乎找不到原因。
//for loop to loop through char of string
for(int i=0; i<expressionString.length(); i++) {
//cast char into ascii int
int ascii = (int) charAt(i);
//appending to token if one of singly operator symbols: *,/,(,),[,]
if(ascii == 40 || ascii == 41 || ascii == 42 || ascii == 47 || ascii == 91 || ascii == 93){
token.append((char) ascii);
tokenList.add(token.toString());
} //append if +, -
else if(ascii == 43 || ascii == 45) {
token.append((char) ascii);
//check next char if + or /, if so append to token again
int nextChar = (char) charAt(i+1);
if(nextChar == 43 || nextChar == 45) {
token.append((char) nextChar);
}
tokenList.add(token.toString());
} //appending to token if it's a num
else if ( ascii >= 48 || ascii <=57) {
token.append((char) ascii);
//check if next char is a num
while ((int) charAt(i+1) >= 48 || (int) charAt(i+1) <= 57) {
//increment i in for loop to check
i++;
token.append((int) charAt(i));
}
tokenList.add(token.toString());
}
//
}
请让我知道这是否是我的代码错误,因为我似乎无法确定问题出在哪里。谢谢!
这是您在该循环中所做操作的简化版本。
public class Main {
public static void main(String[] args) {
String str = "ABCDE";
StringBuilder sb = new StringBuilder();
List<String> list = new ArrayList<>();
for (char c : str.toCharArray()) {
sb.append(c);
list.add(sb.toString()); // <-- Problem! This adds the *entire* contents of the StringBuilder as a new String to the list.
}
System.out.println(list);
}
}
这个程序打印
[A, AB, ABC, ABCD, ABCDE]
这是因为每次我们将 char
附加到 StringBuilder
时,我们都会将 StringBuilder
的 整个 内容添加为ArrayList
.
String
现在假设我们将 "ABCDE"
替换为长度 1000000
的 String
,例如我们将第一行更改为
String str = Stream.generate(() -> "A").limit(1000000).collect(Collectors.joining()); // String of length 1000000
我们现在正尝试创建 1000000 个 String
个长度从 1
到 1000000
的对象,结果可预测。
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOfRange(Arrays.java:3664)
at java.lang.String.<init>(String.java:207)
at java.lang.StringBuilder.toString(StringBuilder.java:407)
at my_package.Main.main(Main.java:17)
如何解决?这取决于你想做什么(我们没有所有的上下文),但我怀疑你不需要 StringBuilder
和 List
.
正如我在评论中指出的那样,您附加到 StringBuilder
而没有从中删除任何内容的事实是可疑的。
StringBuilder
只是 char[]
的包装器,它会在必要时自动调整大小以容纳您尝试追加的新文本。您可以在堆栈跟踪中看到 OOM 发生在这些自动调整大小之一期间。
此问题的一个解决方案是最初分配足够大的缓冲区,然后在向 StringBuilder
添加更多文本之前不需要调整大小:
StringBuilder token = new StringBuilder(MAXIMUM_EXPECTED_SIZE);
问题在于可能难以确定MAXIMUM_EXPECTED_SIZE
;此外,大多数时候您可能会浪费大量内存,因为您几乎没有向缓冲区追加那么多文本。
似乎您实际上并不想在将文本传输到 tokenList
后将其保留在 token
中。您可以使用以下命令从缓冲区中明确删除它:
token.delete(0, token.length());
// or
token.setLength(0);
(实际上,这并没有删除数据,它只是允许后续追加覆盖它)
但这仍然是一种浪费:您根本不需要 StringBuilder
。
考虑一下你如何处理这些数字:
if ( ascii >= 48 || ascii <=57) {
token.append((char) ascii);
//check if next char is a num
while ((int) charAt(i+1) >= 48 && (int) charAt(i+1) <= 57) {
// ^^ NB
//increment i in for loop to check
i++;
token.append((int) charAt(i));
}
tokenList.add(token.toString());
}
你在这里显然想做的是附加第 i
个字符(含)和第 j
个字符(不含)之间的所有内容,其中 j
指向字符串的末尾或非数字字符。所以你可以这样做:
if ( ascii >= 48 || ascii <=57) {
int j = i + 1;
//check if next char is a num
while (j < expressionString.length() && charAt(j) >= '0' && charAt(j) <= '9') {
j++;
}
tokenList.add(expressionString.subString(i, j));
i = j;
}
您可以对其他附加标记执行类似操作。这只是删除了 StringBuilder
的 "middle man",这显然避免了重新分配其内部缓冲区的问题。