简体   繁体   English

为什么在 Java 中将变量重新分配给新字符串需要这么长时间?

[英]Why does reassigning a variable to a new String take so much time in Java?

A few days ago I realized that I had never done any experimentation with measuring the time it takes for a program to run.几天前,我意识到我从来没有做过任何测量程序运行时间的实验。 I decided for fun just to test out some random lines of code for the heck of it using the System.currentTimeMillis() method.为了好玩,我决定使用System.currentTimeMillis()方法测试一些随机的代码行。 I decided to try out something simple, such as merely continuously reassigning a variable.我决定尝试一些简单的事情,比如仅仅不断地重新分配一个变量。 So, I ran the following code:所以,我运行了以下代码:

public class Timer{
    public static void main(String[] args) {
        String s = null;

        final long startTime = System.currentTimeMillis(); // Start the timer

        for (int i = 1; i <= 999999999; i++) { 
            s = i + "Hello";  // This takes around 36 seconds!
        }

        System.out.println(s);

        final long endTime = System.currentTimeMillis(); // End the timer

        System.out.println("Total execution time: " + (endTime - startTime)); // Report total time elapsed
    }
}

I was amazed to see that just continuously reassigning a String variable would take so long.我很惊讶地看到,仅仅不断地重新分配一个String变量会花费这么长时间。 I know the number up to which I'm iterating is quite huge in and of itself, but when I ran other code that still involved the for-loop up to that huge number, the time it took for the program to run was significantly lower.我知道我要迭代的数字本身就非常大,但是当我运行其他仍然涉及到那个巨大数字的 for 循环的代码时,程序运行所需的时间显着降低. For example:例如:

import java.util.ArrayList;

public class TimerArrayList {
    public static void main(String[] args) {

        ArrayList<Integer> list = new ArrayList<Integer>();

        final long startTime = System.currentTimeMillis(); // Start the timer

        // Adding elements to an ArrayList is generally quick (around 4 seconds)
        for (int i = 1; i <= 999999999; i++) {
            list.add(i);
            if (list.size() == 50000)
                list.clear(); // To prevent OutOfMemoryError
        }
        final long endTime = System.currentTimeMillis(); // End the timer
        System.out.println("Total execution time: " + (endTime - startTime)); // Report total time elapsed
    }
}

Adding elements to the ArrayList took only about 4 seconds.将元素添加到 ArrayList 仅需大约 4 秒。 Even faster yet was merely counting up.甚至更快,但只是在数数。 For instance, this code took on average 1.5 milliseconds to run:例如,这段代码平均需要 1.5 毫秒来运行:

public class TimerCounting{
    public static void main(String[] args) {
        final long startTime = System.currentTimeMillis(); // Start the timer

        // Just counting up is super quick (around 1 millisecond)
        int counter = 0;
        for (int i = 1; i <= 999999999; i++) {
            counter = i;
        }

        final long endTime = System.currentTimeMillis(); // End the timer

        System.out.println("Total execution time: " + (endTime - startTime)); // Report total time elapsed
    }
}

So, my question is: why does it take so long just to reassign a String variable?所以,我的问题是:为什么重新分配一个String变量需要这么长时间?

Reassigning of strings is a costly operation and every time we reassign a string a couple of operations actually happen in the background due to the immutable nature of strings.重新分配字符串是一项代价高昂的操作,每次我们重新分配一个字符串时,由于字符串的不可变特性,实际上会在后台发生一些操作。

This is why concatenating Strings many, many times is a bad idea.这就是为什么多次串联字符串是一个坏主意的原因。 Each time you concatenate, your application takes the hit of implicitly making a new String and for this exact purpose we have StringBuffer/StringBuilder每次连接时,您的应用程序都会隐式创建一个新字符串,为此我们有 StringBuffer/StringBuilder

Case 1:情况1:

for (int i = 1; i <= 999999999; i++) {
            counter = i;
}

You are not allocating any new memory to variable counter .您没有将任何新的 memory 分配给变量counter Hence it just take 1 cpu cycle to run counter = i in each iteration.因此,在每次迭代中运行counter = i只需要 1 个 cpu 周期。

Case 2:案例二:

for (int i = 1; i <= 999999999; i++) {
    list.add(i);
    if (list.size() == 50000)
       list.clear(); // To prevent OutOfMemoryError
}

It generally takes more than 1 cpu cycles to add an element in list.在列表中添加一个元素通常需要超过 1 个 cpu 周期。 It first searches for a free space in memory, allocates the space for new element in list and then does some piece of code to actually add the new element in to end of linked list.它首先在 memory 中搜索空闲空间,为列表中的新元素分配空间,然后执行一些代码将新元素实际添加到链表的末尾。

Actually, overall Time taken should be more than 4 seconds .实际上,整体耗时应该超过4 秒 but as you are doing this in batch (allowing old memory to be garbage collected after 50000th element), this will less time when finding new space for new element because of less page swappings.但是当您批量执行此操作时(允许在第 50000 个元素之后对旧的 memory 进行垃圾收集),由于页面交换较少,这将减少为新元素寻找新空间的时间。

Case 3:案例3:


 for (int i = 1; i <= 999999999; i++) { 
   s = i + "Hello";  // This takes around 36 seconds!
 }

This allocates memory from heap as new string is created each time with different value of i.这会从堆中分配 memory,因为每次都会创建具有不同 i 值的新字符串。

Probably in your case, It constanly adds to heap space when creating new string literal until the next garbage collector cycle runs.可能在您的情况下,它会在创建新字符串文字时不断增加堆空间,直到下一个垃圾收集器周期运行。 (Unlike the case of linked list where the memory was cleared on encoutering 50000th element.) (与链表的情况不同,其中 memory 在遇到第 50000 个元素时被清除。)

Hope this clears to some extent !希望这在某种程度上清除!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM