Blog
Skip to end of metadata
Go to start of metadata

A senior Java programozók többségébe az évek tapasztalata mélyen beleégette az általános szabályt, hogy két String típusú változót ne a + operátorral fűzzünk össze, hanem a StringBuffer append metódusával. Ennek az alapja, hogy a String típus speciálisan kezelendő, ha két szöveget összefűzünk, akkor a memóriában három szövegünk lesz, ha ehhez hozzáfűzünk egy negyediket, akkor már öt: a másolgatás lassít és a GC is nehezményezi a többletmunkát. Az idők és a Java verziók változnak, érdemes kissé körüljárni a problémát. A StringBuilder előnyeit nem lehet elvitatni, egy rövid kis tesztprogrammal hamar be lehet bizonyítani, hogy a szövegek összefűzésére nem alkalmas a + operátor. Nézzük a programot:

StringTest.java
public class StringTest
{
  private static final int MAX=500000;

  public long doString()
  {
    long start=System.currentTimeMillis();

    String a="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";

    for (int count=0;count<MAX;count++)
    {
      String aggregated="";
      aggregated+=a;
// ... összesen 16 összefűzés
      aggregated+=a;
    }

    return System.currentTimeMillis()-start;
  }

  public long doStringBuffer()
  {
    long start=System.currentTimeMillis();

    String a="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";

    for (int count=0;count<MAX;count++)
    {
      String aggregated="";
      StringBuffer sb=new StringBuffer();
      sb.append(a);
// ... összesen 16 összefűzés
      sb.append(a);
      aggregated=sb.toString();
    }

    return System.currentTimeMillis()-start;
  }

  public StringTest()
  {
    System.out.println("doString: "+doString()+"ms");
    System.out.println("doStringBuffer: "+doStringBuffer()+"ms");
  }

  public static void main(String[] args)
  {
    new StringTest();
  }
}

A programot futtatva azt kapjuk, hogy a StringBuffer append metódusa ebben az esetben tízszer gyorsabb, mint a + operátor. Persze ezen lehet még gyorsítani, ha a StringBuffer megfelelő kezdő bufferméretet kap - a legjobb, ha ez pont egyezik az összefűzött szöveg méretével, ezért még biztosabban kijelenthetjük: mindig használjunk StringBuffer-t. Módosítsuk kissé a teszteléshez szükséges programot az alábbiak szerint:

public long doString()
{
  long start=System.currentTimeMillis();

  String a="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";

  for (int count=0;count<MAX;count++)
  {
    String aggregated=a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a;
  }
 
 return System.currentTimeMillis()-start;
}

Mint látható, egyszerűen annyit csináltunk, hogy egymás mögé vontuk össze a szövegek összefűzését, s meglepő módon a két metódus közel azonos idő alatt futott le... feltéve, hogy Java 1.5 futtatókörnyezetre fordítottuk, ez alatt ugyanis megmarad a tízszeres különbség, ezzel egyező vagy nagyobb verziószámú célra fordítva azonban a szöveget összefűzése is gyors lesz. Mi lehet az oka? Nézzük csak meg a fordítot .class fájlt egy Java Decompiler programmal.

Java 1.4 fordítás
public long doString()
{
  long l = System.currentTimeMillis();

  String str1 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";

  for (int i = 0; i < 500000; ++i)
  {
    String str2 = str1 + str1 + str1 + str1 + str1 + str1 + str1 + str1 +
                  str1 + str1 + str1 + str1 + str1 + str1 + str1 + str1;
  }

  return (System.currentTimeMillis() - l);
}
Java 1.5 fordítás
public long doString()
{
  long l = System.currentTimeMillis();
  String s = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
  for(int i = 0; i < 0x7a120; i++)
  {
    String s1 = (new StringBuilder()).append(s).append(s).append(s).append(s).
                                      append(s).append(s).append(s).append(s).
                                      append(s).append(s).append(s).append(s).
                                      append(s).append(s).append(s).append(s).toString();
  }

  return System.currentTimeMillis() - l;
}

Mint látni, a fordító kicserélgeti magától a szövegek összefűzését StringBuilder összefűzésre, és ezáltal pont olyan gyors lesz, mintha a StringBuilder append metódusát használnák, mivel pontosan arra fordul, amit a másik metódusban írtunk.

A fentiek fényében, ha egy - a fordító számára összefüggő - sorban használjuk a + operátort, akkor a fordító ezt a kódba már append hívássá fogja fordítani. Az eredmény olvashatóbb forráskód és azonos teljesítmény.

      
      
Page viewed times

5 Comments

  1. Egy kis kiegészítés. A StringBuffer és a StringBuilder majdnem ugyanaz, de azért van némi különbség: Az utóbbit Java 1.5-nél vezették be, a StringBuffer-rel ellentétben nem alkalmas többszálas használatra, de emiatt valamivel gyorsabb. Szóval StringBuilder-t érdemes használni 1.5 és afölötti környezetekben.
    1. Auth Gábor AUTHOR

      Ez stimmel, de 1.4 Java-ra nem tudok olyan kódot fordítani, amiben van StringBuilder, csak olyat, amiben StringBuffer van... :)

      Az 1.5-ös fordító cseréli a + operátort StringBuilder append-re, gondolom azért, mert kismértékben gyorsabb, mint a StringBuffer. A lényeg inkább az, hogy 1.5 esetén az egy sorban elférő + operátorral összefűzött szöveget felesleges lecserélni StringBuffer/StringBuilder appendre, megteszi ezt a fordító és a kód olvashatóbb marad.

  2. Értelemszerűen gyorsabb a StringBuffer, hiszen a háttérben teljesen más folyamatok zajlanak, mint + operátor esetén. Minden egyes összefűzéskor (+ operátor) egy új sztring jön létre az új értékkel, mivel a string konstans, annak értéke nem megváltoztatható. Ami annyit takar a JVM szempontjából, hogy minden esetben egy új objektum jön létre a heap-ben, ami elég költséges művelet.

    Stringbuffer esetén 1 darab objektum jön létre és ennek az objektumnak az értékét változtatod meg. C-ben pl egy pointer értékét növeled, ami a szrting végére mutat, annak értéket adsz, majd a végét egy \0-al lezárod. Valószínűleg javaban is így van implementálva. Innen ered a sokkal gyorsabb működés, mégha a végeredmény ugyan az.

    A GC-re gyakorolt hatássról meg ne is beszéljünk.

  3. Anonymous

    Hat azert a java 1.4 az nagyon matuzsalem. Ha telleg ezt hasznalod meloban 2012-ben, akkor talan egy masik melo kellene (big grin)

    Irtam egyet a temaban en is regebben: http://dummywarhead.blogspot.cz/2011/03/good-old-stringbuffer-vs-stringbuilder.html

    Szoval nem csak a stringbuffer / stringBuilder szamit, hanem az is, hogy elore megmondod-e neki hogy mennyi helyet allokaljon. Bizonyos esetekben ez tobbet szamit mint az, hogy Stringuffer vagy StringBuilder.

    Iteracioban stringet += operatorral appendolni, erre a legtobb statikus analizis eszkoz total felhaborodik (smile)

    1. Auth Gábor AUTHOR

      Négy éves a "topic", csak most migráltam (és módosítottam a formázást), ezért tűnik frissnek...  (smile)

#trackbackRdf ($trackbackUtils.getContentIdentifier($page) $page.title $trackbackUtils.getPingUrl($page))