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.