derletztekick: RegExp in JAVA

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

  1. 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

    1. 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

  2. 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
    
    1. 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

      1. 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

        1. 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

          1. 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

            1. 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

              1. 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.

                1. 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

                  1. 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.

                    1. Hallo Slyh,

                      Danke für Deine ausführliche Antwort. Ich werde versuchen, es zukünftig zu berücksichtigen!

                      Mit freundlichem Gruß
                      Micha

      2. 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

        1. 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

          1. 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

            1. 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

              1. 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