[英]Why does reassigning a variable to a new String take so much time in Java?
幾天前,我意識到我從來沒有做過任何測量程序運行時間的實驗。 為了好玩,我決定使用System.currentTimeMillis()
方法測試一些隨機的代碼行。 我決定嘗試一些簡單的事情,比如僅僅不斷地重新分配一個變量。 所以,我運行了以下代碼:
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
}
}
我很驚訝地看到,僅僅不斷地重新分配一個String
變量會花費這么長時間。 我知道我要迭代的數字本身就非常大,但是當我運行其他仍然涉及到那個巨大數字的 for 循環的代碼時,程序運行所需的時間顯着降低. 例如:
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
}
}
將元素添加到 ArrayList 僅需大約 4 秒。 甚至更快,但只是在數數。 例如,這段代碼平均需要 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
}
}
所以,我的問題是:為什么重新分配一個String
變量需要這么長時間?
重新分配字符串是一項代價高昂的操作,每次我們重新分配一個字符串時,由於字符串的不可變特性,實際上會在后台發生一些操作。
這就是為什么多次串聯字符串是一個壞主意的原因。 每次連接時,您的應用程序都會隱式創建一個新字符串,為此我們有 StringBuffer/StringBuilder
情況1:
for (int i = 1; i <= 999999999; i++) {
counter = i;
}
您沒有將任何新的 memory 分配給變量counter 。 因此,在每次迭代中運行counter = i只需要 1 個 cpu 周期。
案例二:
for (int i = 1; i <= 999999999; i++) {
list.add(i);
if (list.size() == 50000)
list.clear(); // To prevent OutOfMemoryError
}
在列表中添加一個元素通常需要超過 1 個 cpu 周期。 它首先在 memory 中搜索空閑空間,為列表中的新元素分配空間,然后執行一些代碼將新元素實際添加到鏈表的末尾。
實際上,整體耗時應該超過4 秒。 但是當您批量執行此操作時(允許在第 50000 個元素之后對舊的 memory 進行垃圾收集),由於頁面交換較少,這將減少為新元素尋找新空間的時間。
案例3:
for (int i = 1; i <= 999999999; i++) {
s = i + "Hello"; // This takes around 36 seconds!
}
這會從堆中分配 memory,因為每次都會創建具有不同 i 值的新字符串。
可能在您的情況下,它會在創建新字符串文字時不斷增加堆空間,直到下一個垃圾收集器周期運行。 (與鏈表的情況不同,其中 memory 在遇到第 50000 個元素時被清除。)
希望這在某種程度上清除!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.