2009-11-04 124 views
11

我想遍歷字符串中的每個字符,並將字符串的每個字符作爲字符串傳遞給另一個函數。charAt()或子字符串?哪個更快?

String s = "abcdefg"; 
for(int i = 0; i < s.length(); i++){ 
    newFunction(s.substring(i, i+1));} 

String s = "abcdefg"; 
for(int i = 0; i < s.length(); i++){ 
    newFunction(Character.toString(s.charAt(i)));} 

最終結果必須是一個字符串。那麼任何想法會更快或更高效?

回答

15

像往常一樣:它並不重要,但如果你堅持花時間在微優化或者如果你真的想優化的非常特殊的使用情況,試試這個:

import org.junit.Assert; 
import org.junit.Test; 

public class StringCharTest { 

    // Times: 
    // 1. Initialization of "s" outside the loop 
    // 2. Init of "s" inside the loop 
    // 3. newFunction() actually checks the string length, 
    // so the function will not be optimized away by the hotstop compiler 

    @Test 
    // Fastest: 237ms/562ms/2434ms 
    public void testCacheStrings() throws Exception { 
     // Cache all possible Char strings 
     String[] char2string = new String[Character.MAX_VALUE]; 
     for (char i = Character.MIN_VALUE; i < Character.MAX_VALUE; i++) { 
      char2string[i] = Character.toString(i); 
     } 

     for (int x = 0; x < 10000000; x++) { 
      char[] s = "abcdefg".toCharArray(); 
      for (int i = 0; i < s.length; i++) { 
       newFunction(char2string[s[i]]); 
      } 
     } 
    } 

    @Test 
    // Fast: 1687ms/1725ms/3382ms 
    public void testCharToString() throws Exception { 
     for (int x = 0; x < 10000000; x++) { 
      String s = "abcdefg"; 
      for (int i = 0; i < s.length(); i++) { 
       // Fast: Creates new String objects, but does not copy an array 
       newFunction(Character.toString(s.charAt(i))); 
      } 
     } 
    } 

    @Test 
    // Very fast: 1331 ms/ 1414ms/3190ms 
    public void testSubstring() throws Exception { 
     for (int x = 0; x < 10000000; x++) { 
      String s = "abcdefg"; 
      for (int i = 0; i < s.length(); i++) { 
       // The fastest! Reuses the internal char array 
       newFunction(s.substring(i, i + 1)); 
      } 
     } 
    } 

    @Test 
    // Slowest: 2525ms/2961ms/4703ms 
    public void testNewString() throws Exception { 
     char[] value = new char[1]; 
     for (int x = 0; x < 10000000; x++) { 
      char[] s = "abcdefg".toCharArray(); 
      for (int i = 0; i < s.length; i++) { 
       value[0] = s[i]; 
       // Slow! Copies the array 
       newFunction(new String(value)); 
      } 
     } 
    } 

    private void newFunction(String string) { 
     // Do something with the one-character string 
     Assert.assertEquals(1, string.length()); 
    } 

} 
+0

由於這將通過一個字符串,你需要在第一次測試中稍微改變你的測試。 {char [] s =「abcdefg」.toCharArray();}應該在循環內部,或者甚至更好(爲了防止JVM進行聰明的優化,把整個循環和.toCharArray()放在一個單獨的函數中)。衡量所有初始開銷以及循環成本非常重要。特別是因爲性能可能會根據字符串長度從一個切換到另一個。因此測試不同長度的蜇傷也很重要。 – MatBailie 2009-11-04 09:16:10

+5

+1實際回答問題。 – gustafc 2009-11-04 10:56:19

+0

在循環內移動了「s」,並添加了一個assert()以防止newFunction()的JVM優化。當然現在比較慢,但相對的測量結果仍然相同。我的觀點僅僅是如果問題確切地知道,有可能進行優化。重點不是要更改某個操作使用哪個函數,而是要在更高級別上查看該操作以獲得改進,例如,通過緩存 – mhaller 2009-11-04 19:40:08

4

請問newFunction真的需要一個String?它會更好,如果你可以讓newFunction採取char並調用它像這樣:

newFunction(s.charAt(i)); 

這樣一來,就避免了創建一個臨時String對象。

回答你的問題:很難說哪一個更有效。在這兩個示例中,必須創建一個僅包含一個字符的String對象。哪一個更有效率取決於在您的特定Java實現中如何實現String.substring(...)Character.toString(...)。找到它的唯一方法是通過分析器運行你的程序,並查看哪個版本使用更多的CPU和/或更多的內存。通常情況下,你不應該擔心這樣的微觀優化 - 只有當你發現這是造成性能和/或內存問題的原因時,纔會在這方面花費時間。

+0

newFunction真正需要拿一個字符串。除了單個字符,newFunction還可以處理更長的字符串。它以同樣的方式處理它們。我不想重載newFunction來接受char,因爲它在兩種情況下都做同樣的事情。 – estacado 2009-11-04 08:49:05

+1

我完全同意微發展應該避免在發展中,直到它被發現是必要的。我也認爲,作爲一種學習練習,瞭解內存分配和其他「隱藏行爲」是非常重要的。我個人厭倦了有趣的程序員,他們認爲short = performant並且不知不覺地使用了非常低效的算法。不學習這個的人=懶惰。被這個注視的人=慢。有一個平衡點需要打擊。在我看來:) – MatBailie 2009-11-04 08:57:33

+0

@estacado:如果性能是你的驅動程序(如你的帖子所暗示的)在正確的地方進行優化。重載新函數以避免字符串開銷 - 可能是明智的選擇,取決於基於[char]的版本的樣子。在函數週圍扭曲代碼可能會耗費更多時間,效率更低,維護性更差。 – MatBailie 2009-11-04 09:00:31

15

答案是:it doesn't matter

配置您的代碼。這是你的瓶頸嗎?

+0

配置文件以什麼方式?對於內存使用? – 2018-02-08 00:56:16

0

我首先使用String.toCharArray()從源字符串中獲取底層字符[],然後繼續調用newFunction。

但我與加斯帕認爲,這將是最好的,如果你可以只處理字符,避免所有的字符串函數...

+0

String.charAt(i)根據我的意識進行查找。將字符串複製到一個新的數組(這正是我理解String.toCharArray()所要做的)引入了一種新的不同的開銷。將字符串引用重複傳遞給charAt()要比先轉換爲本地數組慢嗎?我懷疑它取決於字符串的長度... – MatBailie 2009-11-04 09:05:09

+0

總是有取捨:)只有OP可以真正說出更有效的東西。 – 2009-11-04 12:03:07

2

您已經發布兩個片斷,我不想說。我同意Will的觀點,認爲它幾乎可以肯定與代碼的整體性能無關 - 如果不是這樣,那麼您可以直接進行更改,並確定硬件上的JVM數據對於您的數據最快。

也就是說,如果首先將字符串轉換爲char數組,然後在數組上執行迭代,則第二個代碼段可能會更好。這樣做會執行一次字符串開銷(轉換爲數組)而不是每次調用。此外,您可以將數組直接傳遞給帶有一些索引的String構造函數,這比使用數組的char out單獨傳遞它(然後變成單字符數組)更有效:

String s = "abcdefg"; 
char[] chars = s.toCharArray(); 
for(int i = 0; i < chars.length; i++) { 
    newFunction(String.valueOf(chars, i, 1)); 
} 

但是爲了強化我的第一點,當你在String.charAt()的每個調用中查看實際避免的內容時 - 它是兩個邊界檢查,一個(懶惰)布爾OR或一個加法。這不會有什麼明顯的差異。在String構造函數中也沒有區別。基本上,這兩個成語在性能方面都很好(既不立即明顯效率低下),所以除非分析器顯示這佔用了大量應用程序的運行時間,否則不應該花更多時間在它們上面工作。即使這樣,您幾乎可以肯定會通過重組您的支持代碼來獲得更多的性能提升(例如,newFunction佔用了整個字符串本身)。 java.lang.String在這一點上已經很好的優化了。

+0

當前jvm中的'substring'實際上使用原始字符數組作爲後備存儲,而您正在啓動一個副本。所以,我的直覺認爲子字符串實際上會更快,因爲memcpy可能會更昂貴(取決於字符串的大小,越大越好)。 – wds 2009-11-04 12:40:40

相關問題