Leer-Elemente an Anfang und Ende von String.split() vermeiden

Carron

Mitglied
Einen wunderschönen guten Abend,

Für die Sortierung von Datumswerten in einer Tabelle betrachte ich einen entsprechenden String, in dem ich alle nicht numerischen Zeichen als Trennzeichen interpretiere und dann die Zahlen einzeln auswerte.
Das funktioniert auch soweit. Allerdings erscheint mir meine Methode aus dem Datums-String ein auswertbares Array zu machen etwas umständlich.

Beispielbelegung von date = "01.06.2010 - 23:45 Uhr"
Ziel-Array value = String[] { "01", "06", "2010", "23", "45" }

Meine aktuelle Herangehensweise:
Java:
	String[] value = date.replaceAll("[^0-9]*[^0-9]", " ").trim().split(" ");

Liefert noch ein leeres Element am Ende:
Java:
	String[] value = date.split("[^0-9]*[^0-9]");

Gibt es einen einfacheren Weg voran- bzw. nachgestellte Trennzeichen zu ignorieren?
Mein Ergebnis-Array soll ja nach Möglichkeit keine Leer-Elemente enthalten...


Viele Grüße
Carron
 
Dumme Frage. warum lässt du das Datum nicht einfach von bereits bestehenden Bibliotheken parsen?
DateTimeFormatter(JodaTime), SimpledateFormat (JRE - nicht multithreaded), FastDateFormat (apache commons)
 
Hallo Anime-Otaku,
eigentlich ging es mir ja nur allgemein um einen Ersatz zu
String.replace(regEx, " ").trim().split(" ")
da ich leider nicht in jedem Fall auf die Leerzeichen in den übrigen Strings verzichten kann und mich dann mit substring() herumschlagen muss.
Aber zu deinem Einwand, warum ich in diesem konkreten Beispiel nicht das Datum parse:
In einer Spalte mit der Datums-Sortieroption lasse ich mehrere Pattern zu. Anzahl und Art der Trennzeichen sind mir dabei egal ([^0-9]*[0-9]).

Beispielsweise:
Java:
 // date format: dd.
 private static final String FORMAT_DD = "%1$td.";
 // date format: dd.mm.
 private static final String FORMAT_DDMM = "%1$td.%1$tm.";
 // date format: dd.mm.YYYY
 private static final String FORMAT_DDMMYYYY = "%1$td.%1$tm.%1$tY";
 // date format: dd.mm. HH:MM
 private static final String FORMAT_DDMM_HHMM = "%1$td.%1$tm. %1$tH:%1$tM";
 // date format: dd.mm.YYYY HH:MM
 private static final String FORMAT_DDMMYYYY_HHMM = "%1$td.%1$tm.%1$tY %1$tH:%1$tM";

um jedoch einen entsprechenden Formatter nutzen zu können brauche ich jeweils das eindeutig anwendbare Pattern. Ich habe mir hier die Brücke geschlagen und es (nach Entfernung der Leer-Elemente auf oben genannte Art und Weise) an der Anzahl der Segmente festgemacht, da die fünf zulässigen Pattern daran eindeutig zu unterscheiden sind. Anschließend vergleiche ich dann die enthaltenen Integer-Werte. Darüber hinaus sollen auch Einträge, die unterschiedliche Pattern erfüllen, einander gegenübergestellt werden und der Eintrag mit der höchsten Spezifizierung (mehr Segmente) wird automatisch als größer angesehen.. Das könnte mit dem entsprechenden Datums-Parser natürlich eleganter von Statten gehen.

Beispiel aktueller Sortierung (aufsteigend):
Code:
 15.
 21.
 13.10.
 09.12.
 25.05.2009
 24.05.2010
 12.06.     - 14:23 Uhr
 12.06.     - 14:29 Uhr
 12.06.     - 15:16 Uhr
 28.11.     - 09:20 Uhr
 25.04.2008 - 22:16 Uhr
 12.02.2010 - 14:10 Uhr

Ich meine: die Pattern können mittels regulärer Ausdrücke auf ihre Anwendbarkeit geprüft werden, da die Segmentanzahl eindeutig über das Format Aufschluss gibt. Ich hatte allerdings bisher nur mit dem SimpleDateFormatter zu tun, der mir in Sachen Pattern nicht variabel genug ist...

Das erscheint mir aber immer noch etwas behäbig:
Java:
 private static final String REGEX_DATETIME_DD = "[^0-9]*[0-9][0-9][^0-9]*";
 private static final String REGEX_DATETIME_DDMM = REGEX_DATETIME_DD
   + "[^0-9][0-9][0-9][^0-9]*";
 private static final String REGEX_DATETIME_DDMMYYYY = REGEX_DATETIME_DDMM
   + "[^0-9][0-9][0-9][0-9][0-9][^0-9]*";
 private static final String REGEX_DATETIME_DDMM_HHMM = REGEX_DATETIME_DDMM
   + "[^0-9][0-9][0-9][^0-9]*" + "[^0-9][0-9][0-9][^0-9]*";
 private static final String REGEX_DATETIME_DDMMYYYY_HHMM = REGEX_DATETIME_DDMMYYYY
   + "[^0-9][0-9][0-9][^0-9]*" + "[^0-9][0-9][0-9][^0-9]*";

In welchem Datums-Parser kann ich denn eine beliebige Anzahl nicht-numerischer Zeichen im Pattern übergeben? (Bei den drei von dir genannten konnte ich das noch nicht erkennen.)
Quasi in die Richtung [^0-9]*dd[^0-9]*[^0-9]mm[^0-9]*[^0-9]YYYY[^0-9]*[^0-9]HH[^0-9]*[^0-9]MM[^0-9]*.

Grüße
Carron

EDIT: wenn ich die beliebigen Zeichen vorher durch ein Einheitstrennzeichen ersetze, kann ich zwar das SimpleDateFormat verwenden, aber das würde mich wieder zu meiner ursprünglichen Frage führen ;)
 
Hallo,

wenn Du Dein Datum nicht unbedingt über reguläre Ausdrücke parsen musst, könntest Du Dir die Methode zum Parsen auch selber schreiben.

In etwa sowas wie dieses hier:

Java:
public Collection<String> parseDigits(String aText) {
		Collection<String> result = new ArrayList<String>();
		
		StringBuffer buffer = new StringBuffer();
		
		for (int i = 0; i < aText.length(); i++) {
			char charAt = aText.charAt(i);

			if(Character.isDigit(charAt)) {
				buffer.append(charAt);
			} else if(buffer.length() > 0) {
				result.add(buffer.toString());
				buffer.delete(0, buffer.length());
			}
		}
		
		return result;
	}

Vielleicht hilft Dir das weiter.

Grüße
twagi
 
Wenn ich dich richtig verstanden habe dann kannst du durch das Pattern bereits erkennen, um welches Datumsformat es sich handelt? Wenn das so ist, dann frag doch mit Pattern ab, ob das dieses Datumsformat ist und dann kannst du den spezifischen Parser verwenden.
 
Entschuldigt bitte den langen (und vor allem nicht wirklich aussagekräftigen) Post zuvor. ;)
Ich habe jetzt deinen Vorschlag umgesetzt und fahre mit dem SimpleDateFormat auch ganz gut.

Abgesehen von den zusätzlichen Konstanten hat das meinen vorherigen Code ziemlich entschlackt.
Java:
    /**
     * Regular expression representing all characters to be treated as
     * separators in <code>COMPARE_DATETIME</code> mode.<br>
     * Current setting: all non-numeric characters</br>
     */
    private static final String REGEX_DATETIME_SEPARATOR = "[^0-9]*[^0-9]";
    
    private static final String REGEX_DATETIME_DD = "[^0-9]*[0-9][0-9][^0-9]*";
    private static final SimpleDateFormat FORMAT_DATETIME_DD = new SimpleDateFormat("dd");
    private static final String REGEX_DATETIME_DDMM = REGEX_DATETIME_DD
            + "[^0-9][0-9][0-9][^0-9]*";
    private static final SimpleDateFormat FORMAT_DATETIME_DDMM = new SimpleDateFormat("dd MM");
    private static final String REGEX_DATETIME_DDMMYYYY = REGEX_DATETIME_DDMM
            + "[^0-9][0-9][0-9][0-9][0-9][^0-9]*";
    private static final SimpleDateFormat FORMAT_DATETIME_DDMMYYYY = new SimpleDateFormat("dd MM yyyy");
    private static final String REGEX_DATETIME_DDMM_HHMM = REGEX_DATETIME_DDMM
            + "[^0-9][0-9][0-9][^0-9]*" + "[^0-9][0-9][0-9][^0-9]*";
    private static final SimpleDateFormat FORMAT_DATETIME_DDMM_HHMM = new SimpleDateFormat("dd MM HH mm");
    private static final String REGEX_DATETIME_DDMMYYYY_HHMM = REGEX_DATETIME_DDMMYYYY
            + "[^0-9][0-9][0-9][^0-9]*" + "[^0-9][0-9][0-9][^0-9]*";
    private static final SimpleDateFormat FORMAT_DATETIME_DDMMYYYY_HHMM = new SimpleDateFormat("dd MM yyyy HH mm");
            
    /**
     * Compares the values of the given column in the two specified entries as
     * date strings in one of the following forms: <code>DD.</code>,
     * <code>DD.MM.</code>, <code>DD.MM.YYYY</code>,
     * <code>DD.MM.YYYY_hh:mm</code>, <code>DD.MM_hh:mm</code><br>
     * Every character that is no number is considered to be a separator.<br>
     * The single values are interpreted as <code>Integer</code>s.
     * 
     * @param targetColumn
     *            selected column
     * @param itemOne
     *            first entry to compare
     * @param itemTwo
     *            second entry to compare
     * @return first entry is smaller, bigger or equals second entry
     */
    int compareDateTime(TableColumn targetColumn, TableItem itemOne,
            TableItem itemTwo) {
        int result;
        // get current position of the target column
        final int columnIndex = getColumnIndex(targetColumn);
        try {
            // read contained dates and compare them
            result = parseDate(itemOne.getText(columnIndex)).compareTo(
                    parseDate(itemTwo.getText(columnIndex)));
            if (SorTable.this.lastDescendingColumn == targetColumn) {
                /*
                 * column was already sorted in descending order: invert sort
                 * direction
                 */
                result *= -1;
            }
        } catch (final ParseException ex) {
            /*
             * at least one of the contained strings do not fit on any of the
             * given date formats
             */
            result = compareText(targetColumn, itemOne, itemTwo);
        }
        return result;
    }

    /**
     * Reads the given <code>String</code> expecting one of the given date
     * formats:<br>
     * <code>DD.</code>, <code>DD.MM.</code>, <code>DD.MM.YYYY</code>,
     * <code>DD.MM.YYYY_hh:mm</code> or <code>DD.MM._hh:mm</code></br> All
     * non-numeric characters are treated to be separators. Empty segments are
     * ignored.
     * 
     * @param dateString
     *            date <code>String</code> to parse
     * @return contained date value
     * @throws ParseException
     *             failed to parse given format or <code>String</code> did not
     *             match any allowed pattern
     */
    private Date parseDate(final String dateString) throws ParseException {
        // replace separators with whitespaces to use trim()
        final String cleanValue = dateString.replaceAll(
                REGEX_DATETIME_SEPARATOR, " ").trim();
        // get the Formatter for the matching pattern
        SimpleDateFormat formatter;
        if (Pattern.matches(REGEX_DATETIME_DD, cleanValue)) {
            formatter = FORMAT_DATETIME_DD;
        } else if (Pattern.matches(REGEX_DATETIME_DDMM, cleanValue)) {
            formatter = FORMAT_DATETIME_DDMM;
        } else if (Pattern.matches(REGEX_DATETIME_DDMMYYYY, cleanValue)) {
            formatter = FORMAT_DATETIME_DDMMYYYY;
        } else if (Pattern.matches(REGEX_DATETIME_DDMM_HHMM, cleanValue)) {
            formatter = FORMAT_DATETIME_DDMM_HHMM;
        } else if (Pattern.matches(REGEX_DATETIME_DDMMYYYY_HHMM, cleanValue)) {
            formatter = FORMAT_DATETIME_DDMMYYYY_HHMM;
        } else {
            // does not fit on any given pattern
            throw new ParseException(dateString, 0);
        }
        // parse the contained date
        return formatter.parse(cleanValue);
    }


Vielen Dank und Grüße
Carron
 
Zuletzt bearbeitet:
SimpledateFormat (JRE - nicht multithreaded)

Ich zitiere mich ungern selbst, aber SimpleDateFormat ist nicht multithreaded-fähig (siehe auch API oder den source). Daher wenn formatter.parse(cleanValue); von mehreren Threads parallel ausgeführt werden kann, kommt es zu seltsamen Ergebnissen (intern wird beim Ausführen von parse ein Calendar-Objekt aufgebaut). Am einfachsten machst du immer einen neuen SimpleDateFormat, der Performanceverlust ist vernachlässigbar oder wenn es nur selten parallel läuft mach einfach ein synchronized drum herum.
Oder du greifst auf eine der anderen genannten libs zurück. Besonders wenn du viel mit Zeiten hantierst ist JodaTime stark zu empfehlen gegenüber der Java internen Zeit API.
 
Zuletzt bearbeitet:
Hallo, eins ist mir nicht ganz klar:

Java:
private static final String REGEX_DATETIME_SEPARATOR = "[^0-9]*[^0-9]";

Wieso setzt du "[^0-9]" nochmal hinter "[^0-9]*"? Willst du damit ausdrücken, dass mindestens ein nicht nummerisches Zeichen vorkommen soll? Wenn ja, es gibt auch ein Zeichen für "mindestens 1x" und das ist "+". D.h. man schreibe "[^0-9]+"

Ebenso z.B. ...

Java:
private static final String REGEX_DATETIME_DDMMYYYY = REGEX_DATETIME_DDMM            + "[^0-9][0-9][0-9][0-9][0-9][^0-9]*";

Du hast 4x hintereiander "[0-9]". Folgender Ausdruck erfordert deine Zeichenauswahl 4x: "[0-9]{4}".


Also deine Ausdrücke sind nicht falsch, aber es geht kürzer...
Siehe auch http://de.wikipedia.org/wiki/Regulärer_Ausdruck
 
Zuletzt bearbeitet:
@HonniCilest:
Danke für die Hinweise, ich habe nur in der Eile einfach das Ganze zusammengehauen und wollte mir das Kürzen für später überlassen (da ich mir im Zug nicht mehr der Meta-Notationen bewusst war und es sollte halt funktionieren^^).

@Anime-Otaku:
Auch dir vielen Dank für den Einwand. Da die compareDateTime()-Methode zwar nur vom Tabellen-Comparator aufgerufen wird bin ich fast immer ohne Parallellläufigkeit unterwegs, aber sicherheitshalber ist das Ganze aber nun auch synchronized. Um mir die Einführung von JodaTime zu sparen, bleibe ich in diesem Fall beim SimpleDateFormat. Da das nur für relativ kleine Tabellen gemacht wird, ist das nicht so zeitkritisch.

Vielleicht kannst du mir aber bitte nochmal sagen inwiefern JodaTime eher zu empfehlen ist. Ist es nur sauberer oder auch performanenter? Wenn letzteres der Fall ist: spürbar schneller?

Mein aktueller Stand sieht jedenfalls folgendermaßen aus:
Java:
    /**
     * Regular expression representing all characters to be treated as
     * separators in <code>COMPARE_DATETIME</code> mode.<br>
     * Current setting: all non-numeric characters
     */
    private static final String REGEX_DATETIME_SEPARATOR = "[\\D]+";

    private static final String REGEX_DATETIME_DD = "[\\D]*[\\d]{2}[\\D]*";
    private static final SimpleDateFormat FORMAT_DATETIME_DD = new SimpleDateFormat(
            "dd");
    private static final String REGEX_DATETIME_DDMM = "[\\D]*[\\d]{2}"
            + "[\\D]+[\\d]{2}[\\D]*";
    private static final SimpleDateFormat FORMAT_DATETIME_DDMM = new SimpleDateFormat(
            "dd MM");
    private static final String REGEX_DATETIME_DDMMYYYY = "[\\D]*[\\d]{2}"
            + "[\\D]+[\\d]{2}" + "[\\D]+[\\d]{4}[\\D]*";
    private static final SimpleDateFormat FORMAT_DATETIME_DDMMYYYY = new SimpleDateFormat(
            "dd MM yyyy");
    private static final String REGEX_DATETIME_DDMM_HHMM = "[\\D]*[\\d]{2}"
            + "[\\D]+[\\d]{2}" + "[\\D]+[\\d]{2}" + "[\\D]+[\\d]{2}[\\D]*";
    private static final SimpleDateFormat FORMAT_DATETIME_DDMM_HHMM = new SimpleDateFormat(
            "dd MM HH mm");
    private static final String REGEX_DATETIME_DDMMYYYY_HHMM = "[\\D]*[\\d]{2}"
            + "[\\D]+[\\d]{2}" + "[\\D]+[\\d]{4}" + "[\\D]+[\\d]{2}"
            + "[\\D]+[\\d]{2}[\\D]*";
    private static final SimpleDateFormat FORMAT_DATETIME_DDMMYYYY_HHMM = new SimpleDateFormat(
            "dd MM yyyy HH mm");

    /**
     * Compares the values of the given column in the two specified entries as
     * date strings in one of the following forms: <code>DD.</code>,
     * <code>DD.MM.</code>, <code>DD.MM.YYYY</code>,
     * <code>DD.MM.YYYY_hh:mm</code>, <code>DD.MM_hh:mm</code><br>
     * Every character that is no number is considered to be a separator.<br>
     * The single values are interpreted as <code>Integer</code>s.
     * 
     * @param targetColumn
     *            selected column
     * @param itemOne
     *            first entry to compare
     * @param itemTwo
     *            second entry to compare
     * @return first entry is smaller, bigger or equals second entry
     */
    int compareDateTime(TableColumn targetColumn, TableItem itemOne,
            TableItem itemTwo) {
        int result;
        // get current position of the target column
        final int columnIndex = getColumnIndex(targetColumn);
        try {
            // read contained dates and compare them
            result = parseDate(itemOne.getText(columnIndex)).compareTo(
                    parseDate(itemTwo.getText(columnIndex)));
            if (SorTable.this.lastDescendingColumn == targetColumn) {
                /*
                 * column was already sorted in descending order: invert sort
                 * direction
                 */
                result *= -1;
            }
        } catch (final ParseException ex) {
            /*
             * at least one of the contained strings do not fit on any of the
             * given date formats
             */
            result = compareText(targetColumn, itemOne, itemTwo);
        }
        return result;
    }

    /**
     * Reads the given <code>String</code> expecting one of the given date
     * formats:<br>
     * <code>DD.</code>, <code>DD.MM.</code>, <code>DD.MM.YYYY</code>,
     * <code>DD.MM.YYYY_hh:mm</code> or <code>DD.MM._hh:mm</code></br> All
     * non-numeric characters are treated to be separators. Empty segments are
     * ignored.
     * 
     * @param dateString
     *            date <code>String</code> to parse
     * @return contained date value
     * @throws ParseException
     *             failed to parse given format or <code>String</code> did not
     *             match any allowed pattern
     */
    private Date parseDate(final String dateString) throws ParseException {
        // replace separators with whitespaces to use trim()
        final String cleanValue = dateString.replaceAll(
                REGEX_DATETIME_SEPARATOR, " ").trim();
        // get the Formatter for the matching pattern
        SimpleDateFormat formatter;
        if (Pattern.matches(REGEX_DATETIME_DD, cleanValue)) {
            formatter = FORMAT_DATETIME_DD;
        } else if (Pattern.matches(REGEX_DATETIME_DDMM, cleanValue)) {
            formatter = FORMAT_DATETIME_DDMM;
        } else if (Pattern.matches(REGEX_DATETIME_DDMMYYYY, cleanValue)) {
            formatter = FORMAT_DATETIME_DDMMYYYY;
        } else if (Pattern.matches(REGEX_DATETIME_DDMM_HHMM, cleanValue)) {
            formatter = FORMAT_DATETIME_DDMM_HHMM;
        } else if (Pattern.matches(REGEX_DATETIME_DDMMYYYY_HHMM, cleanValue)) {
            formatter = FORMAT_DATETIME_DDMMYYYY_HHMM;
        } else {
            // does not fit on any given pattern
            throw new ParseException(dateString, 0);
        }
        synchronized (formatter) {
            // parse the contained date
            return formatter.parse(cleanValue);
        }
    }


Danke.
Carron
 
Zuletzt bearbeitet:
Vielleicht kannst du mir aber bitte nochmal sagen inwiefern JodaTime eher zu empfehlen ist. Ist es nur sauberer oder auch performanenter? Wenn letzteres der Fall ist: spürbar schneller?

http://joda-time.sourceforge.net/index.html da steht das direkt auf der Hauptseite :)

Leider muss ich aber gestehen bisher selbst noch nicht groß in den Genuss von JodaTime gekommen zu sein. Wir benutzen traurigerweise in unserem Produkt in der Firma immer noch Calendar-Objekte zum Speichern von Zeitstempeln und wie es so ist hat man mal nicht eben Zeit die Datenstruktur komplett zu ändern (man müsste auch andere Datenstruktur Sachen ändern). Calendar-Objekte brauchen afaik über 400 byte Speicher!

Und Privat hab ich schon ein wenig damit rumgespielt und von einigen erfahreren Java-Entwicklern wird das auch immer wieder empfohlen. :)
 
Zurück