Java programozás 19. – StringBuilder

StringBuilder, avagy String kezelés profiknak

A String megváltoztathatatlan. Ez tény. Ha mégis módosítjuk, akkor egy új Stringet hozunk létre, az előzőt meg magára hagyjuk. Ez 1-2 lépés esetén nem túl nagy gond, de amikor egy Stringet sokszor kell megváltoztatni, vagy sok kis darabból kell összerakni, akkor ez a módszer rendkívül lassú, pazarló, és erősen kerülendő. Ha sokszor változtatni akarjuk, akkor másra van szükségünk.

StringBuilder

A StringBuilder osztályt kifejezetten azért írták, hogy segítségével a Stringek módosíthatóak legyenek. Ez egy módosítható karakterlánc. Rendkívül hatékonyan tudjuk bővíteni, módosítani a benne lévő tartalmat, amelyből bármikor újra statikus Stringet készíthetünk.

A StringBuilder osztály használatához semmilyen speciális csomagot, osztályt nem kell importálni. Nézzük meg akkor pár példán keresztül, mi mindenre használhatjuk.

StringBuilder deklaráció és értékadás

StringBuilder deklarációja formailag így néz ki:

StringBuilder sb;

A StringBuilder-nek a Stringhez hasonlóan sokféleképp adható érték:

// literálként adjuk meg a tartalmát
StringBuilder sb = new StringBuilder("abrakadabra");

// egy Stringet kap paraméterként
String s = "abrakadabra";
StringBuilder sb = new StringBuilder(s);

StringBuilder sb = new StringBuilder(); // üres is lehet

StringBuilder metódusok

Mint már említettem, a StringBuilder arra szolgál, hogy karakterláncokat kezeljünk, megváltoztassunk. A StringBuilder valamennyire hasonlóságot mutat a Stringekkel, de ez csak pár metódusban nyilvánul meg. Ezek olyan metódusok, melyek nem módosítják a StringBuilder-t.

StringBuilder hossza – length()

Bármely StringBuilder méretét (hosszát) megkaphatjuk, ha meghívjuk a length() metódusát:

StringBuilder sb = new StringBuilder("abrakadabra");
System.out.println( sb.length() );

StringBuilder adott karaktere – charAt()

Egy adott StringBuilder bármelyik karakterét megkaphatjuk a charAt(i) metódussal, ahova az i helyére írjuk be, hogy hányadik karaktert szeretnénk megkapni. A karakterek indexelése a tömbökhöz hasonlóan 0-val kezdődik. Fontos, hogy ez egy karakter típust ad vissza! Bármely StringBuilder első karaktere az sb.charAt(0), az utolsó pedig az sb.charAt( sb.length()-1 )

StringBuilder sb = new StringBuilder("abrakadabra");
sb.charAt(3); // a 4. karakter (3-as index!)
sb.charAt(0); // 1. (üres StringBuilder-nél indexelési hiba!)
sb.charAt( sb.length()-1 ); // utolsó karakter

Keresés StringBuilder-ben – indexOf(), lastIndexOf()

Egyszerűen kereshetünk a StringBuilder-ekben. Kíváncsiak vagyunk, hogy egy karakter vagy szövegrészlet megtalálható-e benne, sőt arra is, hogy hol található. Erre szolgál az s.indexOf() metódus.

StringBuilder sb = new StringBuilder("abrakadabra");
System.out.println( sb.indexOf("rak") ); // 2
//  A 2. indexű (3. karakternél) található a rak szócska.

System.out.println( sb.indexOf("br") ); // 1
/* Az 1. indexű (2. karakternél) található a rak szócska
 * Fontos, hogy az indexOf() mindig az első találat helyét adja meg!
 */

System.out.println( sb.IndexOf("br") > -1 ); // true
/* Itt nem a keresett szöveg helye érdekel, hanem az, hogy benne
 * van-e. Ha a helye -1-től nagyobb, akkor benne van, de nem érdekel,
 * hogy pontosan hol.
 */

System.out.println( sb.indexOf("Br") ); // -1
/* Egy nem létező indexet adott eredményül, vagyis a keresett
 * részlet nem található meg a Stringben.
 */

System.out.println( sb.lastIndexOf("br") ); // 8
/* A 8. indexű (9. karakternél) található a br szócska, de most a
 * keresést hátulról kezdte, és onnan adja vissza az első találatot!
 */

Az indexOf() és lastIndexOf() metódusok alaphelyzetben mindig a StringBuilder elejéről/végéről kezdik a keresést, de meg lehet adni nekik, hogy adott karaktertől kezdjék: indexOf(mit, honnan) Ehhez kapcsolódó feladat lehet, hogy adjuk meg, hol található a második ‘r’ betű a szóban:

StringBuilder sb = new StringBuilder("abrakadabra");

int elso = sb.indexOf("r");

System.out.println( sb.indexOf("r", elso+1 ) );
/* Először megkeressük az első 'r' betűt, majd amikor a másodikat
 * akarjuk megkeresni, akkor megadjuk, hogy az első utáni pozíciótól
 * induljunk. Ezt a két lépést akár össze is vonhatjuk:
 */
System.out.println( sb.indexOf("r", sb.indexOf("r")+1 ) );

System.out.println( sb.lastIndexOf("r", sb.lastIndexOf("r")-1 ) );
/* Ha ugyanezt hátulról végezzük, akkor figyelni kell arra, hogy
 * az első találat előtt kell folytatni, vagyis itt -1
 * kell az első találat helyéhez képest, mivel visszafelé keresünk
 */

StringBuilder részének kinyerése – substring()

Előfordulhat, hogy egy StringBuilder-ből ki kell szednünk egy kisebb részletet. Erre szolgál a substring() metódus. Amikor egy részt akarunk kinyerni egy StringBuilder-ből, akkor meg kell mondanunk, hogy milyen karakter határokhoz (indexek) viszonyítva akarom ezt megkapni. Melyiktől kezdjük, és melyik előtt fejezzük be. Ha csak a kezdő pozíciót adjuk meg, akkor onnantól a StringBuilder végéig az egészet megkapjuk. A substring() mindig String típusú eredményt ad vissza.

StringBuilder sb = new StringBuilder("abrakadabra");
System.out.println( sb.substring(0,5) ); // abrak
System.out.println( sb.substring(2,5) ); // rak
System.out.println( sb.substring(5,8) ); // ada
System.out.println( sb.substring(6) );   // dabra
System.out.println( sb.substring(sb.length()) ); // mindig üres

Ezek a metódusok nagyon ismerősek lehetnek, feltéve, ha olvastad a String témakört. Megmondom őszintén még a magyarázatokat is szinte egy az egyben a onnan vettem át, mert eddig a pontig a két osztály nagyon hasonló.

Jöjjenek akkor azok a metódusok, melyek a StringBuilder igazi erejét adják. Azok, melyek a StringBuilder tartalmát megváltoztatják. Nagyon fontos, hogy ezek valóban az eredeti tartalmat módosítják, onnantól, ami előzőleg volt benne, már nem kaphatjuk vissza.

Hozzáfűzés a StringBuilder végéhez – append()

StringBuilder sb = new StringBuilder(); // üres StringBuilder
sb.append(1);
sb.append(2.0);
sb.append(2.0f);
sb.append(1L);
sb.append(true);
sb.append('c');
sb.append("Bela");
sb.append(sb);
sb.append("abcd".toCharArray());
System.out.println(sb);

A StringBuilder-t az .append() metódussal lehet bővíteni, ezzel tudunk hozzáfűzni a végéhez bármit. A bármit szinte tényleg bármit, mert hozzáfűznivalójuk az összes primitív típust. Igen, boolean-t is, karaktert is! Ezen kívül Stringet, StringBuilder-t, karaktertömböt  is hozzáfűzhetünk. Amikor egy Stringet menet közben kell felépíteni, akkor StringBuilder-t használunk, mert ez a legtakarékosabb, és leggyorsabb megoldás. Az egy dolog, hogy ennek a példának ebben a formában nem sok értelme van, pusztán azt akartam bemutatni, hogy tényleg minden hozzáfűzhető. A lényeg tehát:

A StringBuilder-t szinte bármivel bővítheted az .append() metódussal.

Láthattad, hogy az eredményt közvetlenül ki lehet íratni. De amikor az összefűzött eredményt Stringként szeretnéd tovább használni, akkor a már ismerős toString() metódusra van szükséged:

StringBuilder sb = new StringBuilder(); // üres StringBuilder
sb.append(1);
sb.append(true);
sb.append("Bela");

String s = sb.toString(); // "1trueBela"

Beszúrás StringBuilder-be – insert()

A StringBuilder bővítése nem csak annyit jelent, hogy hozzáfűzünk valamit a végéhez, hanem lehetőségünk van arra, hogy tetszőleges helyre illesszünk be dolgokat. A beszúrás természetesen azt jelenti, hogy ha valahova beszúrunk, akkor a beszúrás pontja utáni dolgok hátrébb tolódnak, de a legfontosabb az, hogy ezzel nem nekünk kell foglalkozni.

StringBuilder sb = new StringBuilder("abrakadabra");

System.out.println( sb );
sb.insert(0,"ABR"); // beszúrás az elejére
System.out.println( sb );
sb.insert(1,"B"); // beszúrás adott helyre
System.out.println( sb );
sb.insert( 5, "ZABRA");
System.out.println( sb );
sb.insert( sb.length(), "A"); // beszúrás a végére
System.out.println( sb );

Beszúrni egyébként az append() metódushoz hasonlóan szinte bármit lehet. Amiket az append()-del hozzáfűzhetünk a StringBuilder-hez, azt az insert()-tel be is szúrhatjuk bárhova. A különbség annyi, hogy az insert() esetén először a beszúrás helyét kell megadni. Az is nyilvánvaló, hogy az append() voltaképp az insert() egy speciális esete:

StringBuilder sb = new StringBuilder("abrakadabra");
sb.insert( sb.length(), "A"); // beszúrás a végére

// ugyanez
StringBuilder sb = new StringBuilder("abrakadabra");
sb.append("A");

StringBuilder egy részének törlése – delete()

Előfordulhat, hogy egy StringBuilder-ből valamilyen részt egyszerűen ki kell törölni.

StringBuilder sb = new StringBuilder("Kiss Bela Jozsef");
sb.delete( 5, 10 );
System.out.println( sb );

sb = new StringBuilder("Kiss Bela Jozsef");
sb.delete( 9, sb.length() );
System.out.println( sb );

A törléskor meg kell adni, hogy melyik karaktertől kezdődően törlünk, valamint meg kell adni, hogy melyik karakter előtt fejezzük be. Ha egy StringBuilder végéről akarunk törölni, akkor a második példa alapján oldhatjuk meg. Megadjuk, hogy honnan kezdjük, és megadjuk, hogy a StringBuilder hossza előtt (vagyis az utolsó karakterrel bezárólag) fejezzük be a törlést.

StringBuilder adott karakterének törlése – deleteCharAt()

Az előző metódushoz hasonlóan ez is töröl a StringBuilder tartalmából, de ez csak egy adott helyen lévő karaktert. Természetesen ez úgy töröl, hogy a mögötte lévő karakterek eggyel előrébb lépnek a sorban.

StringBuilder sb = new StringBuilder("abrakadabra");
sb.deleteCharAt( 0 ); // első karakter törlése
sb.deleteCharAt( 4 ); // 4-es indexű karakter törlése
sb.deleteCharAt( 4 ); // az új 4-es indexű karakter törlése
sb.deleteCharAt( sb.length()-1 ); // utolsó karakter törlése

Ha több egymás melletti karaktert szeretnénk törölni, akkor célszerűbb a delete() metódust használni, ahol megadhatjuk a törlendő karakterek intervallumát. De ha a két első karaktert szeretnénk törölni, akár a következő módszert is használhatjuk:

StringBuilder sb = new StringBuilder("abrakadabra");
sb.deleteCharAt( 0 ); // első karakter törlése
sb.deleteCharAt( 0 ); // az eredetileg második karakter törlése

// helyette ez is szerepelhet
sb.delete( 0, 2 ); // az első két karakter törlése

StringBuilder adott karakterének megváltoztatása – setCharAt()

Amikor egy StringBuilder-ben valamit meg akarunk változtatni, akkor ezt akár karakterenként is megtehetjük. Ez a metódus valahol a charAt() párja, de amíg a charAt() csak visszaadja a StringBuilder adott karakterét, addig a setCharAt() metódussal egy adott indexű karaktert tudunk megváltoztatni valami másra. Az indexnek nyilván valósnak kell lenni, és itt is használhatók azok a sablonok, amelyeket a charAt() esetén megismerhettél. A metódusnak meg kell adni a cserélni kívánt karakter indexét, és azt, hogy mire akarod azt kicserélni. Fontos, hogy csak karakter típusra cserélhetsz!

StringBuilder sb = new StringBuilder("abrakadabra");
sb.setCharAt( 0, 'A' ); // első karakter megváltoztatása
sb.setCharAt( 4, 'C' ); // 4-es indexű karakter megváltoztatása
sb.setCharAt( 4 'G' ); // az új 4-es indexű karakter megváltoztatása
sb.setCharAt( sb.length()-1, 'A' ); // utolsó karakter megváltoztatása

StringBuilder adott részének kicserélése – replace()

Mindenek előtt arra hívnám fel a figyelmet, hogy ez a metódus csak nevében hasonlít a String osztály replace() metódusára, teljesen máshogy működik! Itt egy karakter intervallumot kell megadni, ami azt jelenti, hogy mettől-meddig akarod a StringBuilder adott részét kicserélni valamilyen Stringre. Az intervallum megadása ugyanúgy történik, mint a substring() vagy delete() metódusoknál, vagyis megadott, hogy melyik karaktertől kezdődően és melyik karakter előttig tartson az a rész, amit kicserélsz a 3. paraméterként megadott Stringre. Lássunk akkor példákat:

StringBuilder sb = new StringBuilder("Kiss Janos Jozsef");
System.out.println( sb );

sb.replace(0, 4, "Nagy");
System.out.println( sb ); // Nagy Janos Jozsef

sb.replace(0, 4, "Kovacs");
System.out.println( sb ); // Kovacs Janos Jozsef

sb.replace(7, 12, "Pal"); // Kovacs Pal Jozsef
System.out.println( sb );

A példaprogramban odaírtam az eredményeket, de ettől függetlenül néhány dolgot kiemelnék:

  1. Nem kell azzal foglalkoznod, hogy amit kicserélsz ugyanolyan hosszú legyen, mint amire kicseréled.
  2. Ha egy rövidebb részt hosszabbra cserélsz (lásd 2. csere), akkor a hosszabb új rész odébb tolja az utána lévőket: Nagy -> Kovacs
  3. Ha hosszabb részt cserélsz rövidebbre (lásd 3. csere), akkor a rövidebb új rész előrébb húzza a mögötte lévőket: Janos -> Pal

StringBuilder megfordítása – reverse()

Nem egy feladatban előfordul az, hogy egy String tartalmát meg kell fordítani.

Stringekkel ez a feladat a következőképpen néz ki, feltéve, hogy ismerjük a Stringeket:

String s1 = "abrakadabra";
String s2 = "";

for( int i = s1.length()-1; i > -1; i-- )
{
  s2 = s2.concat(s1.charAt(i)+"");
}
System.out.println(s2);

Vagy esetleg így:

String s1 = "abrakadabra";
String s2 = "";

for( int i = 0; i< s1.length(); i++ )
{
  s2 = s2.concat(s1.charAt(s1.length()-i-1)+"");
}
System.out.println(s2);

A lényeg az, hogy ez egy elég érdekes feladat. Persze a concat() nélkül is megcsinálhatod a += operátorral, de megbeszéltük, hogy akkor új String objektumok jönnek létre, melyeket utána magára fog hagyni a rendszer, és ha hosszú a String akkor még lassú is lesz:

De hogy néz ez ki StringBuilder-rel.

String s = "abrakadabra";
StringBuilder sb = new StringBuilder(s);
sb.reverse();
System.out.println( sb );

A .reverse() metódus bármilyen StringBuilder tartalmát megfordítja és utána azt úgy használjuk, ahogy akarjuk. Kicsit egyszerűbb, nem?

StringBuilder méretének beállítása – setLength()

A StringBuilder mérete akár közvetlenül is beállítható a setLength() metódussal. A metódus egyetlen paramétere egy nem negatív szám, mely azt jelenti, hogy mekkora méretűre szeretnénk beállítani a StringBuilder méretét.

  • Ha a mérete kisebb, mint a tartalom, ami jelenleg benne található, akkor a StringBuilder-ben lávő tartalom csonkolódik, vagyis a méreten felüli részek törlődnek.
  • Ha a megadott méret nagyobb, mint az eddigi, akkor az úgynevezett null karakterrel tölti ki a tartalommal nem rendelkező új részt a megadott méretig.

A méret beállítása nem jelenti azt, hogy a StringBuilder nem bővíthető, ezután is azt csinálunk vele, amit akarunk. Ha azonban a méret bővítés során null karakterek kerültek a végére, akkor ha mondjuk append()-del bővítjük, akkor minden a null karakterek után kerül a StringBuilder végére.

Egy szó, mint száz, láthatod, hogy a StringBuilder kifejezetten arra való, hogy a Stringeket manipuláljuk, megváltoztassuk, vagy akár csak több lépésben felépítsük. Bizonyos manipulációkat sokkal-sokkal hatékonyabban meg tudunk oldani vele, mint Stringekkel. Elég ha csak a beszúrásra, és törlésre gondolunk, melyeket Stringekkel megvalósítani nemcsak bonyolultabb, hanem jóval lassabb is.

Természetesen a StringBuilder-nél is azt az elvet vallom, mint amit a tömböket helyettesítő listákkal kapcsolatban szoktam mondani:

Csak akkor használd, ha már a Stringeket nagyon jól ismered, és használod, mert csak akkor fogod megérteni a StringBuilder igazi erejét, és akkor fogod tudni, mikor kell a Stringek helyett használni.

Következő lecke: ArrayList

Vélemény, hozzászólás?

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük

*

Ez az oldal az Akismet szolgáltatást használja a spam csökkentésére. Ismerje meg a hozzászólás adatainak feldolgozását .