所以我理解Java中的字符串是不可变的。我对如何重复更新某个字符串感兴趣。
例如:
public static void main(String[] args) {
String myString = "hey";
for (int i = 1; i <= 9; i++) {
myString += "hey";
}
}
现在,这不会在Java中工作,因为我已经声明和分配了myString。人们如何绕过Java的不可变字符串(如上例所示)?
我唯一能想到的就是声明另一个字符串。不幸的是,这只会延迟我的问题,因为第二次循环,我将重新分配已经分配的字符串:
public static void main(String[] args) {
String myString = "hey";
String secondString;
for (int i = 1; i <= 10; i++) {
secondString += "hey";
}
}
非常感谢任何建议/解释!
谢谢, Mariogs
答案 0 :(得分:9)
你应该使用StringBuilder
来做这类事情。它旨在将字符串放在一起,而不必经常复制或保留旧的字符串。
public class SimpleGrowingString {
private StringBuilder stringBuilder;
public SimpleGrowingString() {
this.stringBuilder = new Stringbuilder();
}
public void addToString(String str) {
this.stringBuilder.append(str);
}
public String getString() {
return this.stringBuilder.toString();
}
}
虽然字符串是不可变的,但您可以重新分配字符串变量
然后,该变量将引用(指向)分配给它的新String,旧值将标记为Garbage Collection
,并且仅在RAM中挂起,直到垃圾收集器脱离其屁股并将其清除。也就是说,只要没有其他引用它(或它的子部分)仍然在某处。
不可变意味着你不能改变一个字符串本身而不是你不能重新指定现在指向它的值的变量是什么。
例如。
String str = "string one";
字符串"string one"
存在于内存中,无法更改,修改,剪切,添加等。
它是不可变的。
如果我说:
str = "a different string";
然后变量 str
现在引用内存中的不同数据;字符串"a different string"
。
原始字符串"string one"
仍然是我们之前刚刚告诉我们为它指向其他东西的句柄之前的完全相同的字符串。旧的String仍在内存中漂浮,但现在它无头,我们再也无法实际访问该值了。
这导致了垃圾收集的想法。
垃圾收集器不时运行并清除不再使用的旧的,不必要的数据。
它通过检查当前是否存在任何当前指向数据的有效句柄/变量来决定什么是有用的,哪些不是有用的。如果没有使用它,我们就无法再访问它了,它对我们来说是无用的,它会被抛弃。
但你真的不能依靠垃圾收集器按时清理东西,快速或者甚至在你想要的时候让它运行。它在自己的时代发挥着自己的作用。你最好尽量减少工作量,而不是假设它一直在你之后进行清理。
现在你已经有了一个非常基本的垃圾收集基础,我们可以谈谈你为什么不把字符串加在一起:
将+
用于字符串(以及设计StringBuilder
和StringBuffer
的原因)的一个大问题是它会创建大量的字符串。各地的弦乐!根据您的使用情况,它们可能会相对快速地成为垃圾收集的候选者,但如果处理不当,它们仍然可能导致膨胀,特别是当涉及循环时,垃圾收集器运行时,只要它感觉很好,我们可以&#39;真的控制着我们不能说事情没有失控,除非我们自己阻止他们这样做。
执行简单的字符串连接:
"a String" + " another String"
实际上导致内存中有三个字符串:
"a String", " another String" and "a String another String"
将此扩展为一个简单,相当短的循环,你可以很快看到事情如何变得无法控制:
String str = "";
for (int i=0; i<=6; i++) {
str += "a chunk of RAM ";
}
在每个循环中意味着我们在内存中:
0:
"a chunk of RAM "
1:
"a chunk of RAM "
"a chunk of RAM a chunk of RAM"
2:
"a chunk of RAM "
"a chunk of RAM a chunk of RAM"
"a chunk of RAM a chunk of RAM a chunk of RAM"
3:
"a chunk of RAM "
"a chunk of RAM a chunk of RAM "
"a chunk of RAM a chunk of RAM a chunk of RAM "
"a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM "
4:
"a chunk of RAM "
"a chunk of RAM a chunk of RAM "
"a chunk of RAM a chunk of RAM a chunk of RAM "
"a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM "
"a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM"
5:
"a chunk of RAM "
"a chunk of RAM a chunk of RAM "
"a chunk of RAM a chunk of RAM a chunk of RAM "
"a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM "
"a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM"
"a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM "
6:
"a chunk of RAM "
"a chunk of RAM a chunk of RAM "
"a chunk of RAM a chunk of RAM a chunk of RAM "
"a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM "
"a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM"
"a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM "
"a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM "
依此类推......你可以看到它的去向以及它到达那里的速度有多快。
如果您使用字符串进行循环并将其组合使用StringBuilder
或StringBuffer
它们的用途是什么。
串联字符串不是因为字符串的不变性而导致大量浪费的RAM的唯一方法。
就像我们无法添加到字符串的末尾一样,我们也不能砍掉尾巴。
String moderatelyLongString = "a somewhat lengthy String that rambles on and on about nothing in particular for quite a while even though nobody's listening";
如果我们想要只使用此String的一部分,我们可以使用String.substring()
方法:
String snippedString = moderatelyLongString.substring(0, 13);
System.out.println(snippedString):
>> a somewhat le
好的,那该怎么了?
好吧,如果我们想要转储第一个长字符串但挂在短字符串上你可能会认为我们可以说:
moderatelyLongString = null;
你可能会认为这会让长串被遗弃而孤独,在角落里哭泣,等待GC来到寒冷的地方,但是你错了。
由于我们在snippedString
中仍然掌握了较长链的几个字符,整个moderatelyLongString
会留在堆上浪费空间。
如果你想这样做但是减掉了无用的重量你想要做的就是复制缩短的部分,但不要保持与长篇的关系:
String aNicerMorePoliteShortString = new String(moderatelyLongString.substring(0, 13));
这会从长整数中获取短字符串的副本,该字符串是它自己的独立字符数组,并且与那个纠缠不清的hanger-on无关,即长字符串。
现在这样做会将长字符串标记为可用于Collection,因为我们没有剩余的关系:
moderatelyLongString = null;
如果你只是想在每次迭代的循环中显示一个不同的String,你想要做的只是(重新)使用一个变量,以便尽快释放内存中的所有旧字符串并且尽可能快地用于垃圾收集。在循环之外声明您的变量,然后在每次迭代时将其重新分配:
String whatYouWantToUse;
for (int i=0; i<100; i++) {
whatYouWantToUse = someStringyGettyMethod();
howYouWantToUseIt(whatYouWantToUse);
}
每次循环循环时,它都会为变量分配一个新值,该值将旧值抛出到垃圾堆上以便垃圾收集器及时清理,或者,您知道,只要它可能被打扰。
可以说,做上述方法的一个更好的方法是永远不要试图抓住String - 只要将它从我们得到它的地方直接传递到它想要的地方:
for (int i=0; i<100; i++) {
howYouWantToUseIt(someStringyGettyMethod());
}
但请注意过度优化此类事物,因为可读性几乎总是比紧凑性更重要 大多数编译器比我们以前更聪明,或者至少比我更聪明。他们可以找到可以对你的代码进行的所有伟大的快捷方式和缩小,并以比我们凡人希望实现的更宏伟的方式应用他们的魔法。
如果你试图过多地简化你的代码,那么你剩下的就是两种不可读的代码,而不是一种有用的,快速的和优化的版本,而另一种是可维护的,而Johnny则具有令人生畏的习惯。每25秒抽一次,两张桌子就可以跟着。
答案 1 :(得分:2)
this won't work in Java because I've already declared and assigned myString
你错了,它仍然可以工作,但每次你附加到字符串时它都会生成一个新的字符串。
如果您在追加/添加时不想生成新字符串,那么StringBuilder
就是解决方案。
<强>样品:强>
public static void main(String args[]) {
StringBuilder sb = new StringBuilder("hey");
for (int i = 1; i <= 9; i++) {
sb.append("hey");
}
}
答案 2 :(得分:2)
不可变并不意味着它不会工作,它只是意味着你创建的对象不会被修改..但是String变量可以被分配给另一个对象(通过连接前面的字符串而创建的新字符串+ =&# 34;哎&#34;。)
如果你想像一个可变对象那样做,那么只需使用StringBuilder append()方法。
答案 3 :(得分:1)
虽然Java中的字符串是不可变的,但上面的第一个示例会起作用,因为它每次循环都会创建一个新的String对象,并将新创建的字符串分配给myString
:< / p>
public static void main(String[] args) {
String myString = "hey";
for (int i = 1; i <= 9; i++) {
myString += "hey";
}
System.out.println(myString); // prints "heyheyheyheyheyheyheyheyheyhey"
}
虽然这有效,但由于对象创建效率低下。对于具有更多迭代的循环,或者当连接更长的字符串时,性能可能是一个问题 - 因此有更好的方法来实现它。
在Java中连接字符串的一种更好的方法是使用StringBuilder。这里的代码适合使用StringBuilder:
public static void main(String[] args) {
StringBuilder builder = new StringBuilder(50); // estimated length
for (int i = 1; i <= 9; i++) {
builder.append("hey");
}
String myString = builder.toString(); // convert to String when needed
System.out.println(myString); // prints "heyheyheyhey..."
}
StringBuilder有一个支持数组作为缓冲区,只要附加的长度超过缓冲区的大小,它就会被扩展。在这种情况下,我们首先分配50个字符。
在实际情况中,您可以根据输入的大小设置StringBuilder缓冲区的初始大小,以最大限度地减少对昂贵的缓冲区扩展的需求。
答案 4 :(得分:0)
您的代码完美无缺。虽然不建议像你那样处理字符串。 看看Java的StringBuilder:http://docs.oracle.com/javase/7/docs/api/java/lang/StringBuffer.html
借助StringBuilder,您可以修改字符串。
答案 5 :(得分:0)
当您在问题中执行工作代码时,每次附加到内存时,只需在内存中创建一个新的string
。
这意味着每次向string
添加内容时,内存中都会出现新字符串对象,这意味着它还有一个新的内存地址。
这是因为字符串确实是不可变的。
如果您只想创建一次字符串对象,则应使用StringBuilder
,否则此解决方案可以正常工作。
建议使用StringBuilders来构建字符串,就像你一样 - 修改很多字符串。因为修改一个字符串(即创建许多新字符串)会在你的记忆中进行大量的读写操作。