4.5 Zerlegen von Zeichenketten
 
Die Java-Bibliothek bietet einige Klassen und Funktionen, um große Zeichenketten in kleinere nach bestimmten Mustern zu zerlegen. In diesem Kontext sind die Begriffe Token und Delimiter zu nennen: Ein Token ist ein Teil eines Strings, das durch bestimmte Trennzeichen (engl. delimiter) von anderen Token getrennt wird. Nehmen wir als Beispiel den Satz »Moderne Musik ist Instrumentespielen nach Noten« (Peter Sellers). Wählen wir Leerzeichen als Trennzeichen, lauten die einzelnen Token »Moderne«, »Musik« und so weiter.
Die Java-Bibliothek bietet eine Reihe von Möglichkeiten zum Zerlegen von Zeichenfolgen, von denen einige in den nachfolgenden Unterkapiteln vorgestellt werden:
|
split() von String. Aufteilen mit durch reguläre Ausdrücke beschriebenem Delimiter. |
|
Scanner. Schöne Klasse zum Ablaufen einer Eingabe mit unterschiedlichen Typen. |
|
StringTokenizer. Der Klassiker. Delimiter sind nur einzelne Zeichen. |
|
BreakIterator. Erkennen von Sätzen. |
4.5.1 Splitten von Zeichenketten mit split() aus Pattern
 
Mit der Funktion split() aus Pattern kann eine Trennfolge definiert werden, die eine Zeichenkette in Teilzeichenketten zerlegt, ähnlich wie es der StringTokenizer macht. Der StringTokenizer ist jedoch beschränkt auf einzelne Zeichen als Trennsymbole, während die Methode split() einen regulären Ausdruck zur Beschreibung der Trennsymbole verwendet:
final class java.util.regex.Pattern
implements Serializable
|
|
String[] split( CharSequence input )
Zerlegt die Zeichenfolge input in Teilzeichenketten, wie es das aktuelle Pattern-Objekt befiehlt. |
|
String[] split( CharSequence input, int limit )
Wie split(CharSequence), doch nur höchstens limit viele Teilzeichenketten. |
Beispiel Datumsformate sollen in der Reihenfolge Tag, Monat, Jahr eingegeben werden. Die Trennzeichen sollen jedoch entweder Punkt, Minus oder Slash sein. Damit lässt sich ein Pattern aufbauen, das folgendes Format hat:
|
Pattern p = Pattern.compile( "[/.-]" );
Zum Zerlegen wird dann split() mit dem Datum-String übergeben.
String[] date1 = p.split( "12–3–1973" );
System.out.printf( "%s.%s.%s%n", date1[0], date1[1], date1[2] );
String[] date2 = p.split( "12.3.1973" );
System.out.printf( "%s.%s.%s%n", date2[0], date2[1], date2[2] );
String[] date3 = p.split( "12/3/1973" );
System.out.printf( "%s.%s.%s%n", date3[0], date3[1], date3[2] );
In diesem Fall funktioniert dies auch noch mit new StringTokenizer(...,"/.-"), doch längere Trennsymbole wie := oder :: würden mit StringTokenizer nicht mehr funktionieren.
|
4.5.2 split() in String
 
In der String-Klasse gibt es ebenso eine Funktion split(). Sie ist eine Objektmethode, die die aktuelle Zeichenkette, die das String-Objekt repräsentiert, zerlegt. Diese Methode benötigt jetzt umgekehrt den regulären Ausdruck als Argument. Die Implementierung delegiert jedoch die eigentliche Arbeit an das Pattern-Objekt:
public String[] split( String regex, int limit )
{
return Pattern.compile( regex ).split( this, limit );
}
public String[] split( String regex )
{
return split( regex, 0 );
}
final class java.lang. String
implements CharSequence, Comparable<String>, Serializable
|
|
String[] split( String regex )
Zerlegt die aktuelle Zeichenkette mit dem regulären Ausdruck. |
|
String[] split( String regex, int limit )
Zerlegt die aktuelle Zeichenkette mit dem regulären Ausdruck, liefert jedoch maximal begrenzt viele Teilzeichenfolgen. |
4.5.3 Die Klasse Scanner
 
Noch flexibler als ein StringTokenizer ist die in Java 5 eingeführte Klasse java.util.-Scanner. Die Klasse zerlegt eine Zeichenkette wie StringTokenizer in Token, aber der Delimiter ist ein regulärer Ausdruck und nicht nur ein einzelnes Zeichen.
Zum Aufbau der Scanner-Objekte bietet die Klasse einige Konstruktoren an, die die zu zerlegenden Zeichenfolgen unterschiedlichen Quellen entnimmt, etwa einem String, einem Datenstrom – beim Einlesen von der Kommandozeile wird das System.in sein –, einem File-Objekt oder diversen NIO-Objekten. (Falls ein Objekt vom Typ Closeable dahinter steckt, wie einen Writer, sollte mit close() der Scanner geschlossen werden, der das close() zum Closeable weiterweitet. Beim String ist das nicht nötig.)
final class java.util. Scanner
implements Iterator<String>
|
|
Scanner( String source ) |
|
Scanner( File source ), Scanner( File source, String charsetName ) |
|
Scanner( InputStream source ), Scanner( InputStream source, String charsetName ) |
|
Scanner( Readable source ) |
|
Scanner( ReadableByteChannel source ) |
|
Scanner(ReadableByteChannel source, String charsetName )
Erzeugt ein neues Scanner-Objekt. |
Der Nächste bitte
Nach dem Erzeugen des Scanner-Objekts liefert die Methode next() die nächste Zeichenfolge, wenn denn ein hasNext() die Rückgabe true ergibt. (Das sind dann auch die Methoden der Schnittstelle Iterator, wobei remove() nicht implementiert ist.)
Beispiel Von der Standardeingabe soll ein String gelesen werden.
Scanner scanner = new Scanner( System.in );
String s = scanner.next();
|
Neben der next()-Methode, die nur ein String als Rückgabe liefert, bietet die Klasse diverse next<Typ>()-Funktionen an, die das nächste Token einlesen und in ein gewünschtes Format konvertieren, etwa in ein double bei nextDouble(). Über gleich viele hasNext<Typ>()-Funktionen lässt sich erfragen, ob ein weiteres Token von diesem Typ folgt.
Beispiel Die einzelnen nextXXX()- und hasNextXXX()-Methoden:
Listing 4.6
ScannerDemo.java, main()
Scanner scanner = new Scanner( "JavaTutor 12 1973 12,03 True 123456789000" );
System.out.println( scanner. hasNext () ); // true
System.out.println( scanner. next () ); // JavaTutor
|
System.out.println( scanner. hasNextByte () ); // true
System.out.println( scanner. nextByte () ); // 12
System.out.println( scanner. hasNextInt () ); // true
System.out.println( scanner. nextInt () ); // 1973
System.out.println( scanner. hasNextDouble () ); // true
System.out.println( scanner. nextDouble () ); // 12.03
System.out.println( scanner. hasNextBoolean () ); // true
System.out.println( scanner. nextBoolean () ); // true
System.out.println( scanner. hasNextLong () ); // true
System.out.println( scanner. nextLong () ); // 123456789000
System.out.println( scanner. hasNext () ); // false
|
Sind nicht alle Tokens interessant, überspring Scanner skip(Pattern pattern) bzw. Scanner skip(String pattern) sie – Delimiter werden nicht beachtet.
Trennzeichen definieren
useDelimiter() setzt für die folgenden Filetier-Vorgänge den Delimiter. Um nur lokal für das nächste Zerlegen einen Trenner zu setzen, lässt sich mit next(String) oder next(Pattern) ein Trenn-Muster angeben.
Beispiel Der String s enthält eine Zeile wie a := b. Uns interessieren der linke und rechte Teil.
String s = "Url := http://java-tutor.com"
Scanner scanner = new Scanner( s ).useDelimiter( "\\s*:=\\s*" );
System.out.println( scanner.next() ); // Url
System.out.println( scanner.next() ); // <a href="http://java-tutor.com">http://java-tutor.com</a>
|
Die Methode hasNextLine() testet, ob noch eine weitere Zeile eingelesen werden kann. Wenn ja, liefert nextLine() diese Zeile. Mit findInLine(String) bzw. findInLine-(Pattern), wird der Scanner angewiesen, nach dem Muster nur bis zum nächsten Zeilenendezeichen zu suchen; Delimiter ignoriert er.
Beispiel Sucht mit findInLine() nach einem Muster.
String text = "Hänsel-und-Gretel\ngingen-durch-den-Wald";
Scanner scanner = new Scanner( text ).useDelimiter( "-" );
System.out.println( scanner.findInLine( "Wald" ) ); // null
System.out.println( scanner.findInLine( "ete" ) ); // "ete"
System.out.println( scanner.next() ); // "l" "gingen"
System.out.println( scanner.next() ); // "durch"
|
final class java.util. Scanner
implements Iterator<String>
|
boolean hasNext()
boolean hasNext( Pattern pattern ), boolean hasNext( String pattern )
boolean hasNextBigDecimal(), boolean hasNextBigInteger()
boolean hasNextBigInteger( int radix )
boolean hasNextBoolean()
boolean hasNextByte(), boolean hasNextByte( int radix )
boolean hasNextDouble(), boolean hasNextFloat()
boolean hasNextInt(), boolean hasNextInt( int radix )
boolean hasNextLine()
boolean hasNextLong(), boolean hasNextLong( int radix )
boolean hasNextShort(), boolean hasNextShort( int radix )
Liefert true, wenn ein Token des gewünschten Typs gelesen werden kann.
String next()
String next( Pattern pattern ), String next( String pattern )
BigDecimal nextBigDecimal(), BigInteger nextBigInteger()
BigInteger nextBigInteger( int radix )
boolean nextBoolean()
byte nextByte(), byte nextByte( int radix )
double nextDouble(), float nextFloat()
int nextInt(), int nextInt( int radix )
String nextLine()
long nextLong(), long nextLong( int radix )
short nextShort(), short nextShort( int radix )
Liefert das nächste Token.
Die Basis für Zahlen ändert Scanner useRadix(int) und erfragt int radix(). Mit findWithinHorizon(Pattern/String, int) lässt sicht sich eine Obergrenze von Code-Points (vereinfacht ausgedrückt: Zeichen) angeben. Liefert die Methode in dieser Grenze kein Token, liefert sie null und setzt auch den Positionszeiger nicht weiter.
Landessprachen
Auch ist die Scanner-Klasse in der Lage, die Dezimalzahlen unterschiedlicher Sprachen zu erkennen. Mit dem passenden Locale-Objekt wird dann auch nextDouble("12,34") funktionieren.
Scanner scanner = new Scanner( "12,34" ).useLocale( Locale.GERMAN );
System.out.println( scanner.nextDouble() ); // 12.34
Das klingt logisch, funktioniert aber auch ohne useLocale(Locale.GERMAN)! Der Grund ist einfach: Der Scanner setzt die Locale vorher standardmäßig auf Locale.getDefault(). Das bedeutet aber wiederum, dass eine übliche Zahl wie 12.34 nicht erkannt wird und als java.util.InputMismatchException gemeldet wird.
final class java.util. Scanner
implements Iterator<String>
|
|
Scanner useLocale()
Setzt die Sprache zum Erkennen der lokalisierten Zahlen, inbesondere der Fließkommazahlen. |
|
Locale locale()
Liefert die eingestellte Sprache. |
IO-Fehler während des Parsens
Bezieht der Scanner die Daten von einem Readable, kann es Ein-/Ausgabefehler in Form von IOExceptions geben. Die Methoden vom Scanner geben diese Fehler nicht weiter, sondern speichern ihn. Mit IOException ioException() liefert der Scanner diesen Fehler oder null, falls es keinen Fehler gab.
4.5.4 StringTokenizer
 
Die Klasse StringTokenizer hilft uns, eine Zeichenkette in Token zu zerlegen, wobei die Klasse nicht an bestimmte Trenner gebunden ist – sie können vielmehr völlig frei gewählt werden. In der Voreinstellung sind Tabulator, Leerzeichen und Zeilentrenner die Delimiter. Der StringTokenizer ist auf einzelne Zeichen als Trennsymbole beschränkt, während die Methode split() und die Klassen um Pattern einen regulären Ausdruck zur Beschreibung der Trennsymbole erlauben:
Hinweis In der Implementierung von StringTokenizer können nur einzelne Zeichen als Trenner verwendet werden. Es sind keine Zeichenfolgen wie »:=« denkbar.
|
 Hier klicken, um das Bild zu Vergrößern
Beispiel Um einen String mithilfe eines StringTokenizer-Objekts zu zerlegen, wird dem Konstruktor der Klasse der zu unterteilende Text als Argument übergeben:
StringTokenizer tokenizer;
tokenizer = new StringTokenizer( "Schweigen kann die grausamste Lüge sein." );
Der Text ist auschließlich ein Objekt vom Typ String.
|
Sollen andere Zeichen als die voreingestellten Trenner den Satz zerlegen, kann dem Konstruktor als zweiter String eine Liste von Trennern übergeben werden. Jedes Zeichen, das in diesem String vorkommt, fungiert als einzelnes Trennzeichen:
StringTokenizer st;
st = new StringTokenizer( "Blue=0000ff\nGreen:00ff00\n", "=:\n" );
Um den Text abzulaufen, gibt es die Methoden nextToken() und hasMoreTokens().1
Die Methode nextToken() liefert das nächste Token im String. Ist kein Token mehr vorhanden, wird eine NoSuchElementException ausgelöst. Damit wir frei von diesen Überraschungen sind, können wir mit der Methode hasMoreTokens() nachfragen, ob noch weitere Token vorliegen.
Beispiel Das folgende Stück Programmtext zeigt die leichte Benutzung der Klasse:
String s = "Faulheit ist der Hang zur Ruhe ohne vorhergehende Arbeit";
StringTokenizer tokenizer = new StringTokenizer( s );
while ( tokenizer.hasMoreTokens() )
System.out.println( tokenizer.nextToken() );
|
Neben den beiden Konstruktoren existiert noch ein Dritter, der auch die Trennzeichen als eigenständige Bestandteile bei nextToken() übermittelt.
 Hier klicken, um das Bild zu Vergrößern
class java.util. StringTokenizer
implements Enumeration<Object>
|
|
StringTokenizer( String str, String delim, boolean returnDelims )
Ein String-Tokenizer für str, wobei jedes Zeichen in delim als Trennzeichen gilt. Ist returnDelims gleich true, so werden die Trennzeichen beim Aufzählen mit zurückgegeben. |
|
StringTokenizer( String str, String delim )
Ein String-Tokenizer für str, wobei alle Zeichen in delim als Trennzeichen gelten. Entspricht dem Aufruf von this(str, delim, false); |
|
StringTokenizer( String str )
Ein String-Tokenizer für str. Entspricht dem Aufruf von this(str, " \t\n\r\f", false); Die Trennzeichen sind Leerzeichen, Tabulator, Zeilenende und Seitenvorschub. |
|
boolean hasMoreTokens(), boolean hasMoreElements()
Testet, ob weitere Token verfügbar sind. hasMoreElements() implementiert die Methode für Enumeration. |
|
String nextToken(), Object nextElement()
Liefert das nächste Token vom String-Tokenizer. nextElement() existiert nur, damit der Tokenizer als Enumeration benutzt werden kann. Der weniger spezifische Ergebnistyp Object macht eine Typumwandlung erforderlich. |
|
String nextToken( String delim )
Die Delimiter-Zeichen werden erst neu gesetzt, und anschließend wird das nächste Token geholt. |
|
int countTokens()
Zählt die Anzahl der noch möglichen nextToken()-Methodenaufrufe. Die aktuelle Position wird dadurch nicht berührt. Der Aufruf der Funktion ist nicht billig. |
nextToken() und nextElement() können eine NoSuchElementException auslösen.
4.5.5 Der BreakIterator als Wort- und Satztrenner
 
Zeichenketten werden von Benutzern aus ganz unterschiedlichen Gründen durchlaufen. Einige wollen jedes Zeichen bearbeiten, andere wiederum suchen in der Zeichenkette nach Wort- oder Satztrennern. Sun sieht für diese Aufgabe im Java-Paket java.text die Klasse BreakIterator vor. Dieser Iterator lässt sich mit statischen Funktionen erzeugen, die optional auch nach einer Sprache trennen. Keine übergebene Sprache bedeutet automatisch die gefundene Standardsprache.
abstract class java.text. BreakIterator
implements Cloneable
|
|
static BreakIterator getCharacterInstance() |
|
static BreakIterator getCharacterInstance( Locale where )
Trennt nach Zeichen. Entspricht einer Iteration über charAt(). |
|
static BreakIterator getLineInstance() |
|
static BreakIterator getLineInstance( Locale where )
Trennt nach Zeilen. |
|
static BreakIterator getSentenceInstance() |
|
static BreakIterator getSentenceInstance( Locale where )
Trennt nach Sätzen. |
|
static BreakIterator getWordInstance() |
|
static BreakIterator getWordInstance( Locale where )
Trennt nach Wörtern. Trenner wie Leerzeichen und Satzzeichen gelten ebenfalls als Wörter. |
Das nächste Beispiel zeigt, wie ohne großen Aufwand durch Zeichenketten gewandert werden kann. Die Verwendung eines StringTokenizers ist nicht nötig. Einmal sollen die Wörter und einmal die Sätze ausgegeben werden. Die Hilfsfunktion out() gibt die Abschnitte der Zeichenkette bezüglich eines BreakIterators aus.
 Hier klicken, um das Bild zu Vergrößern
Listing 4.7
StringVerpfluecker.java
import java.text.BreakIterator;
import java.util.Locale;
public class StringVerpfluecker
{
static void out( String s, BreakIterator iter )
{
int last = iter.first();
int next = iter.next(); // erstes Wort einlesen.
for ( ; next != BreakIterator.DONE; next = iter.next() )
{
System.out.println( s.subSequence( last, next ) );
last = next;
}
}
public static void main( String[] args )
{
String helmutKohl = "Ich weiß, dass ich 1945 fünfzehn war und 1953 achtzehn.";
BreakIterator iterator = BreakIterator.getWordInstance();
iterator.setText( helmutKohl );
out( helmutKohl, iterator );
helmutKohl = "Das deutsche Volk hat nun mal beschlossen, weniger Kinder zu zeugen. "+
"Das ist eine Sache, die wir nicht einmal den Sozialdemokraten anhängen können.";
iterator = BreakIterator.getSentenceInstance( Locale.GERMAN );
iterator.setText( helmutKohl );
out( helmutKohl, iterator );
}
}
Auf den ersten Blick ergibt ein BreakIterator von getCharacterInstance() keinen großen Sinn, denn für das Ablaufen einer Zeichenkette ließe sich viel einfacher eine Schleife nehmen und mit chatAt() arbeiten. Der BreakIterator kann jedoch korrekt mit Unicode 4 umgehen, wo zwei chars ein Unicode-4 Zeichen bilden.
1 Die Methode hasMoreElemente() ruft direkt hasMoreTokens() auf und wurde nur implementiert, da ein StringTokenizer die Schnittstelle Enumeration implementiert.
|