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:
- Nem kell azzal foglalkoznod, hogy amit kicserélsz ugyanolyan hosszú legyen, mint amire kicseréled.
- 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
- 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.
