RegExp in JAVA
derletztekick
- programmiertechnik
Hallo,
ich versuche mich gerade ein wenig an regulären Ausdrücken - mehr schlecht als recht.
Ich habe einen simplen String, aus dem ich zwei Teile haben möchte. Gleich vorweg, split() wäre hier wohl die bessere Wahl und mir durchaus geläufig.
Einfaches Beispiel:
String str = "1234=Hallo Welt";
String rep = str.replaceAll("^([0-9]+?)[=]{1}(.+?)", "$1 und $2");
System.out.println(rep); // 1234 und Hallo Welt
Was muss ich aber tun, damit ich nur die Ziffern bekomme?
Nur der Text geht mit: String rep = str.replaceAll("^([0-9]+?)[=]{1}(.+?)", "$2");
.
Warum geht dann String rep = str.replaceAll("^([0-9]+?)[=]{1}(.+?)", "$1");
nicht so, wie ich es vermutet hätte oder ist nur replaceAll() ungünstig für mein Vorhaben?
Mit freundlichem Gruß
Micha
Sieh dir doch das Packet java.util.regex an und dann sollte auch alles klappen. Habs mir aber zugegebenermaßen selbst gerade gegooglet.
Ein Tutorial hierzu findest du zum Beispiel unter: http://www.bastie.de/index.html?/java/mjregular/index.html
Hallo Rafael,
Sieh dir doch das Packet java.util.regex an und dann sollte auch alles klappen.
Wenn es so einfach wäre...
Mit freundlichem Gruß
Micha
gudn tach!
String str = "1234=Hallo Welt";
String rep = str.replaceAll("^([0-9]+?)[=]{1}(.+?)", "$1 und $2");
System.out.println(rep); // 1234 und Hallo Welt
bloss als hinweis:
[=]{1} laesst sich kuerzer schreiben als = und
^([0-9]+?)=(.+?)
is hier das gleiche wie
^([0-9]+?)=(.)
denn (.+?) ist non-greedy.
gematcht wird also bloss "1234 und H". der rest bleibt einfach unangetastet.
> Was muss ich aber tun, damit ich nur die Ziffern bekomme?
> Nur der Text geht mit: `String rep = str.replaceAll("^([0-9]+?)[=]{1}(.+?)", "$2");`{:.language-java}.
ja, das ersetzt "1234 und H" durch "H", wodurch du "Hallo Welt" erhaeltst.
> Warum geht dann `String rep = str.replaceAll("^([0-9]+?)[=]{1}(.+?)", "$1");`{:.language-java} nicht so, wie ich es vermutet hätte
das ersetzt "1234 und H" durch "1234", macht zusammen: "1234allo Welt".
> oder ist nur replaceAll() ungünstig für mein Vorhaben?
kommt darauf, wie kompliziert der string bzw. dein genaues vorhaben ist.
String rep = str.replaceAll("=", " und ");
wuerde dir vielleicht schon ausreichen und dann braeuchstest du nicht mal zwangslaeufig regexps einsetzen.
prost
seth
Hallo seth,
erstmal Dank für Deine Hilfe, es geht nun - warum auch immer ;-)
gematcht wird also bloss "1234 und H". der rest bleibt einfach unangetastet.
Ja, das habe ich bemerkt. Also das er den ersten Buchstaben (H) immer überschireben hat
ja, das ersetzt "1234 und H" durch "H", wodurch du "Hallo Welt" erhaeltst.
Dann scheint das Ergebnis eher zufällig richtig zu sein? Ziel war es, den gesamten Text durch "HAllo Welt" zu erstzen. Ich habe nun einfach das Plus durch ein Mal (Stern) ersetzt (in Deiner verkürzten Schreibweise):
das Bringt:
rep = str.replaceAll("^([0-9]+)=(.*)", "$1");
--> 1234
rep = str.replaceAll("^([0-9]+)=(.*)", "$2");
--> Hallo welt
Das wäre erstemal das, was ich wollte - also auch das, wa mit split() im Array liefern würde, wenn ich am = trennen würde.
Warum es das nun ist (oder sein könnte?) versteh ich trotzdem nicht. Laut der Übersichtstabelle bedeutet .+ "Zusammensetzung aus beliebiges Zeichen und beliebig viele davon, jedoch mindestens eines" Demnach schlußfolgerte ich, nachdem =-Zeichen muss noch mindestens ein Zeichen kommen oder eben mehrere. Dem scheint dann nicht so zu sein?
oder ist nur replaceAll() ungünstig für mein Vorhaben?
kommt darauf, wie kompliziert der string bzw. dein genaues vorhaben ist.
So kompliziert, wie ich es hier beschrieb. Ich will beide Teile haben und in eine Hashtable als key-value-Paar haben. Gelesen wird der String aus einer Datei.
Mit freundlichem Gruß
Micha
gudn tach!
Warum es das nun ist (oder sein könnte?) versteh ich trotzdem nicht. Laut der Übersichtstabelle bedeutet .+ "Zusammensetzung aus beliebiges Zeichen und beliebig viele davon, jedoch mindestens eines" Demnach schlußfolgerte ich, nachdem =-Zeichen muss noch mindestens ein Zeichen kommen oder eben mehrere. Dem scheint dann nicht so zu sein?
du musst das fragezeichen beruecksichtigen. fuer erklaerung siehe selfhtml.
oder ist nur replaceAll() ungünstig für mein Vorhaben?
kommt darauf, wie kompliziert der string bzw. dein genaues vorhaben ist.
So kompliziert, wie ich es hier beschrieb. Ich will beide Teile haben und in eine Hashtable als key-value-Paar haben. Gelesen wird der String aus einer Datei.
und warum willst du split nicht verwenden?
prost
seth
Hallo seth,
du musst das fragezeichen beruecksichtigen. fuer erklaerung siehe selfhtml.
Demnach ist er bei (.+?) bereits "zufriedengestellt", wenn er ein einzelnes Zeichen findet, ohne ? nimmt er soviel wie möglich? Es liegt dann weniger am + oder *, wie ich gerade bemerkt habe, sondern am "?".
und warum willst du split nicht verwenden?
Einen echten Grund kann ich Dir nicht nennen. Ich prüfe jede Zeile auf das Muster und wende es an:
else if (line.matches("^([0-9]+)=(.+)")) {
entries.put(Integer.parseInt(line.replaceAll("^([0-9]+)=(.+)", "$1")), line.replaceAll("^([0-9]+)=(.+)", "$2"));
}
Ich spar mir so den Umweg über das temp. Array und einer Schleife, weil mehr als ein = im String sein könnte, in der Bedingung, ob man das jedoch als echten Vorteil gelten lassen kann, bezweifle ich. Somit bleibt unterm Strich nur, ich wollte mich mal mit den reg. Ausdrücken befassen als Antwort übrig. Ist es denn deutlich langsamer?
Mit freundlichem Gruß
Micha
Hallo,
Ich spar mir so den Umweg über das temp. Array
Wofür brauchst du ein temporäres Array?
und einer Schleife, weil mehr als ein = im String sein könnte,
Was soll passieren, wenn mehrere (oder gar kein) = drin sind/ist?
Gruß
Slyh
Hallo Slyh,
Ich spar mir so den Umweg über das temp. Array
Wofür brauchst du ein temporäres Array?
Gar nicht, das liefert split mir einfach ;-)
Was soll passieren, wenn mehrere (oder gar kein) = drin sind/ist?
Wenn keins drin ist, nichts. Wenn mehr als eins drin ist, gehört jedes weitere zum String und bleibt unberücksichtigt. Zu split fällt mir dieser Weg ein:
String str = 1234=Hallo Welt 3+5=8;
String[] tmpArr = str.split("=");
int key = Integer.parseInt(tmpArr[0]);
String value = "";
for (int i=1; i<tmpArr.length; i++)
value+=tmpArr[i];
Mit freundlichem Gruß
Micha
Hallo,
> String str = 1234=Hallo Welt 3+5=8;
> String[] tmpArr = str.split("=");
> int key = Integer.parseInt(tmpArr[0]);
> String value = "";
> for (int i=1; i<tmpArr.length; i++)
> value+=tmpArr[i];
Und dieser Code ist ein guter Beweis dafür, warum ein Split hier
ziemlich ungeeignet ist. Zumindest solange auch Gleichheitszeichen
im Wert auftauchen dürfen. :)
Gruß
Slyh
PS: Strings über den Plus-Operator zu verketten, ist böse. Besonders hier.
Hallo Slyh,
Und dieser Code ist ein guter Beweis dafür, warum ein Split hier
ziemlich ungeeignet ist.
Danke ;-)
PS: Strings über den Plus-Operator zu verketten, ist böse. Besonders hier.
Oh, begründest Du das auch ein wenig? Ich verkette sie immer mit Plus wenn es nötig ist, eine Alternative habe ich ja gar nicht, oder?
Mit freundlichem Gruß
Micha
Hallo,
PS: Strings über den Plus-Operator zu verketten, ist böse. Besonders hier.
Oh, begründest Du das auch ein wenig? Ich verkette sie immer mit Plus wenn es nötig ist, eine Alternative habe ich ja gar nicht, oder?
Doch, hast du. StringBuffer und neuerdings auch StringBuilder.
Kurzer Hindergrund:
Wenn du String über + verkettest, wird mindestens ein String-Objekt
erzeugt, nämlich das, das den neuen String enthält, das aus den beiden
verketteten besteht.
Das ist bei einem einzelnen Aufruf auch völlig in Ordnung. Wenn man
aber mehrere Strings z.B. in einer Schleife verketten will, werden
hierfür sehr viele String-Objekte angelegt und vor allem auch deren
Inhalt ständig kopiert.
Beispiel:
Du hängst über eine Schleife zehnmal "abcdefg" an einen String an.
Beim ersten Durchlauf wird ein String mit 7 Zeichen erzeugt und
"abcdefg" hineinkopiert. Beim zweiten Durchlauf wird ein String-Objekt
mit 14 Zeichen erzeugt und das "abcdefg" aus dem letzten Durchgang
und noch einmal "abcdefg" hineinkopiert. Beim dritten Durchgang wird
wieder ein String-Objekt (21) erzeugt, in das "abcdefgabcdefg" und
dann "abcdefg" hineinkopiert wird. Beim 10. Durchgang wird ein String
mit 70 Zeichen erzeugt und zuerst der letzte String (9x"abcdefg") und
noch einmal "abcdefg" hineinkopiert.
Wie man sich leicht denken kann, verschwendet das Umkopieren eine
Menge Speicher und dauert auch eine nicht unerhebliche Zeit. Zudem
werden die String-Objekte von Durchlauf 1-9 nur kurzzeitig verwendet,
bleiben aber erstmal im Speicher stehen, bis der Gargabe Collector
sie abräumt.
Insgesamt ist String-Konkatenation über den Plus-Operator daher
eine schlechte Idee.
Wenn mehrere Strings zu einem großen String verknüpft werden sollen,
verwendet man StringBuffer. Dieses besteht intern aus einem char-Array
mit anfänglicher Größe, in das die anzuhängenden Werte einfach
reinkopiert werden können. Reicht der Platz im char-Array nicht aus,
wird es automatisch vergrößert.
Schau dir einfach mal die Klasse StringBuffer/StringBuilder an.
Außerdem ist in jedem Java-Buch der Nachteil von String-Verknüpfung
mit dem Plus-Operator dokumentiert und die Verwendung von
StringBuffer/StringBuilder erläutert.
Was kein Problem ist, ist Code wieder dieser:
String s1 = "abc";
String s2 = "def";
String s3 = "ghi";
String s4 = "jkl";
String s = s1 + s2 + s3 + s4;
Hier erzeugt der Java-Compiler beim Kompilieren automatisch ein
StringBuffer-Objekt und fügt die einzelnen Strings mit der append()-
Methode an.
Im obigen Beispiel (10x-Schleife) kann er dies nicht.
Gruß
Slyh
PS: Wenn ich noch richtig weiß, ist es sogar noch schlimmer als oben
beschrieben. Faktisch legt der Compiler nämlich bei jeder Operation
der Form "s1 + s2" zuerst ein StringBuffer-Objekt an, initialisiert
es mit dem Inhalt von s1, ruft dann append() mit s2 auf und konvertiert
den Inhalt anschließend über toString() wieder in einen String. Es
wird also zusätzlich zum eigentlich unnötigen String auch noch bei
jedem Schleifendurchlauf ein unnötiger StringBuffer erzeugt.
Hallo Slyh,
Danke für Deine ausführliche Antwort. Ich werde versuchen, es zukünftig zu berücksichtigen!
Mit freundlichem Gruß
Micha
Hallo,
So kompliziert, wie ich es hier beschrieb. Ich will beide Teile haben und in eine Hashtable als key-value-Paar haben. Gelesen wird der String aus einer Datei.
Wieso dann nicht primitiv über substring()? Das ist doch 100mal schneller
als die Verwendung regulärer Ausdrücke.
int assignPos = s.indexOf('=');
if (assignPos != -1) {
key = s.substring(0, assignPos).trim();
value = s.substring(assignPos + 1).trim();
}
Ungetestet.
Gruß
Slyh
gudn tach!
Wieso dann nicht primitiv über substring()? Das ist doch 100mal schneller als die Verwendung regulärer Ausdrücke.
ich weiss nicht, wie's in java ist, aber in perl ist das afais nicht so.
beispiel:
#!/usr/bin/perl
use strict;
use Benchmark qw(:all) ;
my $s = '1234=Hallo Welt';
my $tmp;
my %h;
my $results = timethese(10_000_000, {
'regexp_split' => sub{%h=split /=/, $s;},
'regexp_br' => sub{$s=~/^([0-9]+)=(.+)\z/; %h=($1=>$2);},
'boring' => sub{$tmp=index($s, '='); %h=(substr($s, 0, $tmp)=>substr($s, $tmp+1));},
},'none'
);
cmpthese($results);
ergebnis:
Rate regexp_br boring regexp_split
regexp_br 390168/s -- -16% -31%
boring 464253/s 19% -- -18%
regexp_split 569152/s 46% 23% --
hier ist die split-methode, die schnellste, die mit dem wenigsten code und imho auch die fuer den menschen am besten lesbare.
prost
seth
Hallo,
ohje, noch ein Karlsruher. Gibt's im SelfHTML-Forum auch Poster, die
nicht aus Karlsruhe kommen? :-)
ich weiss nicht, wie's in java ist, aber in perl ist das afais nicht so.
»»
[...]
»»
Rate regexp_br boring regexp_split
regexp_br 390168/s -- -16% -31%
boring 464253/s 19% -- -18%
regexp_split 569152/s 46% 23% --
Beeindruckend. Ich kann nur spekulieren, daß Perl in solchen Sonderfällen
auf nativen Code zurückgreift oder daß String-Operationen besonders
langsam sind oder dergleichen.
Ich habe mal kurz einen kleinen Mikro-Benchmark in Java geschrieben,
der die Split- und die Substring-Methodik miteinander vergleicht.
Auf meinem Rechner benötigt Java für 10.000.000 Splits ca. 22,23 Sekunden.
Für Substring werden nur 1,61 Sekunden benötigt. (Inkl. dem Anlegen
eines Arrays mit 2 Elementen.)
Umgerechnet ergibt dies etwa 450.000 Splits pro Sekunden und ca. 6.211.000
Substrings pro Sekunde.
System: JDK1.5.0_08 auf Athlon 64 X2 (Dual Core) 3800+ Prozessor.
Mikrobenchmarks sind natürlich immer mit Vorsicht zu genießen. Aber
eine gewisse Tendenz ist durchaus ablesbar.
Gruß
Slyh
Hallo,
Auf meinem Rechner benötigt Java für 10.000.000 Splits ca. 22,23 Sekunden.
Für Substring werden nur 1,61 Sekunden benötigt. (Inkl. dem Anlegen
eines Arrays mit 2 Elementen.)
Mit vorkompiliertem Pattern sind es für's Split dann "nur" noch ca. 16,6 Sekunden,
was etwa 602.000 Splits pro Sekunde sind.
Gruß
Slyh
Hallo Ihr zwei,
nun, da der String aus einer Datei eingelsen wird, wird wohl das pure Einlesen bereits viel mehr Zeit in Anspruch nehmen als das Trennen selbst.
Vll hätte ich etwas weiter ausholen sollen? Ich habe mir bei verschiedenen Programmen angesehen, wie die mehrere Sprachen anbieten und bin auf solche Sprachdateien gestoßen. Allg. Aufbau sieht so aus:
[Menu]
0=Datei
1=Einstellungen
2=?
[File]
0=Öffnen
1=Beenden
Es existiert also eine Art Kategorie, gefolgt von Unterpunkten. Ich lese nun als jede Zeile ein und bearbeite diese, sofern sie in das Kategorie- oder Unterpunktmuster passt und steck sie in eine Hashtable. Das sieht dann "bildlich" so aus:
ht = [[Menu, [0, Datei]
[1,Einstellungen]
[2,?]],
[File, [0, Öffnen]
[1,Beenden]]
]
Da die Indizes nicht in folge kommen müssen oder nicht Null am Anfang sind, stecken auch die Unterpunkte in so einer Tabelle, sodass ich einen Eintrag über die beiden Schlüssel bekomme.
String category = null;
Hashtable<Integer, String> entries = new Hashtable<Integer, String>();
while ((line = br.readLine()) != null) {
if (line.trim().length() != 0) {
if (line.matches("\\[(.+?)\\]")) {
if (category != null && category.length() > 0 && !this.ht.containsKey(category) && entries.size() > 0) {
this.ht.put(category, entries);
entries = new Hashtable<Integer, String>();
}
category = line.replaceAll("\\[(.+?)\\]", "$1");
}
else if (line.matches("^([0-9]+)=(.+)")) {
entries.put(Integer.parseInt(line.replaceAll("^([0-9]+)=(.+)", "$1")), line.replaceAll("^([0-9]+)=(.+)", "$2"));
}
}
}
if (category != null && category.length() > 0 && !this.ht.containsKey(category) && entries.size() > 0)
this.ht.put(category, entries);
Mit freundlichem Gruß
Micha