Splitte opp tekst i enkeltkomponenter
Som verktøy for å splitte opp strenger i enkeltkomponenter, blir det for
ny kode anbefalt å bruke String
-klassens split
-metode
eller noen av verktøyene i pakken
java.util.regex.
Det er også mulig å bruke en
Scanner
til slik oppsplitting. En Scanner
kan som kilde for det som skal
splittes opp også bruke en hvilken som helst input-strøm, for
eksempel en fil. Dessuten kan den gjenkjenne forskjellige datatyper.
Tidligere var det objekter av klassen StringTokenizer
som ble
brukt til å foreta oppsplitting av tekst i enkeltkomponenter. Av hensyn til
kompatibilitet bakover finnes klassen fortsatt, men anbefales ikke brukt i ny kode.
Bruk av StringTokenizer
-objekter er beskrevet i notatet
Lese tekst ord for ord ved hjelp av en StringTokenizer
.
For å definere skille mellom enkeltkomponentene i den teksten som skal
splittes opp, brukes det et regulært uttrykk. (For en
StringTokenizer
brukes det bare en streng.) Hva som menes med
regulære uttrykk er beskrevet i notatet
Regulære uttrykk.
Metoden finnes i to versjoner. Som regel er det den som har bare én
parameter vi har bruk for:
public String[] split(String regex)
Parameteren regex
er det regulære uttrykket som definerer
skillet mellom enkeltkomponentene. Metoden returnerer er en array som inneholder de enkelte
komponentene som oppsplittingen har gitt. Arrayen kan være av vilkårlig størrelse.
Merk deg imidlertid at oppsplittingen kan inneholde komponenter som består av
tomme strenger. Men eventuelle tomme strenger på slutten vil ikke bli tatt med.
Dersom den strengen
som skal splittes opp ikke har noen delstreng som passer med det regulære
uttrykket, vil den returnerte arrayen ha ett element og dette inneholder den
streng som skulle splittes opp. Metoden har samme virkning som å bruke den
andre versjonen av metoden (se nedenfor) med grense-parameter lik null.
Eksempel 1
String test = "Dette er en test på bruk av split-metoden.";
String[] resultat = test.split( "\\s" );
for ( int i = 0; i < resultat.length; i++ )
System.out.println( resultat[ i ] );
vil gi følgende utskrift:
Dette
er
en
test
på
bruk
av
split-metoden.
Husk at det regulære uttrykket \s
betyr såkalt
whitespace. Siden uttrykket inngår i en streng når det brukes
i metodekallet, må det være en ekstra bakoverskråstrek foran. (Se notatet
om regulære uttrykk.) Bindestreken og punktumet i den siste komponenten som
ble skrevet ut, er ikke whitespace. Derfor får vi det ut på denne måten.
Eksempel 2
Strengen "20:00:00"
resulterer i følgende oppsplitting med de
regulære uttrykk som er angitt i tabellens første kolonne:
Regulært uttrykk | Resultat |
---|
: | { "20", "00", "00" } |
0 | { "2", ":", "", ":" } |
Legg merke til at vi i det siste tilfelle får en tom streng innimellom,
men at den tomme strengen på slutten ikke blir tatt med i returen.
Den andre versjonen av split
-metoden har to parametre:
public String[] split(String regex, int grense)
Første parameter har samme betydning som i første versjon av metoden.
Parameteren grense
kontrollerer antall ganger det regulære uttrykket
skal brukes. Parameteren vil derfor påvirke størrelsen på den returnerte arrayen.
Dersom grensen n er større enn null, vil det regulære uttrykket bli
brukt høyst n - 1 ganger. Den returnerte arrayen kan dermed ikke få
mer enn n plasser. Siste element i arrayen vil da inneholde all tekst
i kildestrengen som følger etter siste match til det regulære uttrykket.
Dersom n er null eller negativ, vil det regulære uttrykket bli brukt
så mange ganger som mulig og den returnerte arrayen kan ha hvilken som helst
lengde. Det samme er tilfelle når n er null, men eventuelle tomme
strenger på slutten vil da bli droppet.
Eksempler
Følgende eksempler viser hvordan forskjellige verdier for grenseparameteren
påvirker oppsplittingen av strengen "20:00:00"
med de
regulære uttrykk som er angitt i tabellens første kolonne.
Regulært uttrykk | Grense | Resultat |
---|
: | 2 | { "20", "00:00" } |
: | 5 | { "20", "00", "00" } |
: | -2 | { "20", "00", "00" } |
0 | 5 | { "2", ":", "", ":", "" } |
0 | -2 | { "2", ":", "", ":", "" } |
0 | 0 | { "2", ":", "", ":" } |
Legg merke til når den returnerte arrayen inneholder tomme strenger på
slutten, og når den ikke gjør det.
En Scanner
kan brukes til å splitte opp tekstlig input i
enkeltkomponenter. Som input kan det brukes en streng eller en strøm, for
eksempel en fil eller input fra tastaturet. Byte fra inputstrømmen blir
konvertert til tegn. Det kan enten være i samsvar med default-tegnsettet til
den underliggende plattform (noe som er unødvendig å spesifisere), eller i
samsvar med et spesifisert tegnsett. I tillegg til å splitte opp i strengkomponenter
har en Scanner
evne til å gjenkjenne verdier av javas primitive
datatyper (gitt på strengform), unntatt typen char
, slik at den
kan returnere neste verdi av en spesifisert type. Også typene
BigInteger
og BigDecimal
kan den gjenkjenne og
returnere. I numeriske verdier blir det påstått at det også kan være
tusen-separator i samsvar med den formatteringsstandard som gjelder på stedet
(eller på en spesifisert lokalitet). I Norge
er det mellomrom (blankt tegn) som er den korrekte tusen-separatoren. Strengen
"32 767" skulle derfor på korrekt måte bli lest som int
-verdien
32767
. Det viser seg imidlertid at dette ikke stemmer. Strengen vil
bli tolket som to separate heltallsverdier. Derimot blir komma på riktig måte
tolket som desimaltegn i en streng som "8,5"
.
Dersom annet ikke er spesifisert, er det såkalt whitespace som brukes som
skille mellom komponentene når oppsplittingen foretas. Men det er ved hjelp av
metoden useDelimiter
mulig å spesifisere hvilket som helst
regulært uttrykk som skille mellom komponentene. Ønsker vi seinere å gå tilbake
til default-virkemåten med bruk av whitespace som skille, kan vi oppnå det ved
kall på metoden reset()
.
Når vi er ferdig med å bruke en Scanner
, er det nødvendig å
lukke den ved kall på dens close()
-metode for å signalisere at vi
er ferdig med å bruke dens eventuelle underliggende strøm. Denne vil da også
automatisk bli lukket.
Eksempel 1
I programmet
Forfatternavn.java
som er gjengitt nedenfor, blir det fra tekstfeltet input
lest inn navn på formatet
Etternavn, Fornavn (eventuelt et eller flere mellomnavn).
Navnene skrives ut igjen i tekstfeltet output
på formatet
Fornavn (eventuelt et eller flere mellomnavn) Etternavn.
Eksempel: Innlest navn
Beauvoir, Simone de
blir skrevet ut igjen som
Simone de Beauvoir
I programmet er navnene kalt forfatternavn, men det kan selvsagt være hvilke
som helst andre navn på samme format. Driverklasse for programmet finnes i
fila
Tokentest.java
Innlesing, konvertering og utskrift av navn skjer i metoden
konverterNavn()
. Den splitter opp det innleste navn ved hjelp
av en Scanner
der komma og mellomrom oppfattes som skille
mellom ordene. Først trekkes etternavnet ut av den innleste strengen og lagres
i en egen variabel. Deretter trekkes det ut fornavn og eventuelle mellomnavn
så lenge det finnes flere. De tilføyes etter tur i en egen variabel for
fornavn og mellomnavn. Til slutt blir strengen som inneholder etternavnet
tilføyd, og det hele vises i tekstfeltet for utskrift. For øvrig blir det
mellom de enkelte navnene lagt inn mellomrom.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class Forfatternavn extends JFrame
{
private JTextField input, output;
public Forfatternavn()
{
super( "Forfatternavn" );
input = new JTextField( 30 );
output = new JTextField( 30 );
output.setEditable( false );
Container c = getContentPane();
c.setLayout( new FlowLayout() );
c.add( new JLabel( "Forfatternavn (ettenavn, fornavn):" ) );
c.add( input );
c.add( new JLabel( "Forfatternavn (fornavn etternavn):" ) );
c.add( output );
input.addActionListener( new Inputlytter() );
setSize( 600, 100 );
setVisible( true );
}
public void konverterNavn()
{
String navn = input.getText(); //strengen som skal konverteres
Scanner leser = new Scanner( navn );
leser.useDelimiter("[,\\s]+"); //komma eller whitespace er skille
String etternavn = "";
if ( leser.hasNext() ) // strengen var ikke tom
etternavn = leser.next(); // henter ut etternavnet
String fornavn = "";
while ( leser.hasNext() ) //henter ut fornavn, så mange det er
fornavn += leser.next() + " ";
leser.close();
if ( !etternavn.equals( "" ) )
output.setText( fornavn + etternavn );
else
output.setText( "Ingen navn lest inn." );
}
private class Inputlytter implements ActionListener
{
public void actionPerformed( ActionEvent e )
{
if ( e.getSource() == input )
konverterNavn();
}
}
}
Merknad I eksemplet ovenfor brukes strengen
"[,\\s]+"
som det regulære uttrykk som definerer skille
mellom komponentene. Det betyr at skille er enten minst et blankt tegn (egentlig
minst et såkalt whitespace), eller minst et komma, eventuelt etterfulgt av et antall
blanke.
Eksempel 2
Programmet
ScanSum
som er gjengitt
nedenfor er hentet fra Sun's Java Tutorial, men litt tilpasset norske forhold.
Programmet leser inn tall (i form av sifferstrenger) fra fila
tall.txt
og summerer dem.
(Slik programmet er skrevet, må både programfil og tallfil ligge i en
underkatalog som heter tekst
.) Tallfila har følgende innhold:
8,5
32767
3,14159
1000000,1
Legg merke til at den inneholder en blanding av hele tall og desimaltall,
og at det er brukt komma som desimaltegn. Når programmet blir kjørt, vil
det skrive ut følgede resultat:
1032778.74159
Programkode:
package tekst;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Scanner;
public class ScanSum
{
public static void main(String[] args) throws IOException
{
Scanner s = null;
double sum = 0;
try
{
s = new Scanner(
new BufferedReader(
new FileReader("tekst/tall.txt")));
while (s.hasNext())
{
if (s.hasNextDouble())
sum += s.nextDouble();
else
s.next();
}
}
finally
{
s.close();
}
System.out.println(sum);
}
}
Eksempel 3: Testprogram for regulære uttrykk
Dersom vi ønsker å sjekke om en gitt tekststreng er i samsvar med et
bestemt mønster, er det ikke alltid så lett å finne ut hvordan vi skal skrive
et korrekt regulært uttrykk for å beskrive vedkommende mønster. Programmet
RegexTestHarness
gjengitt nedenfor er en bearbeidet versjon av et program fra Sun's Java
Tutorial. Programmets brukervindu er vist på følgende bilde.
I første tekstfeltet skriver man inn det regulære uttrykket (mønsteret)
man vil teste på. Det blir lest inn og registrert når man trykker på returtasten.
I det andre tekstfeltet skriver man inn den teksten man vil det skal søkes i.
Deretter trykker man på returtasten.
Søket blir da utført og resultatet blir skrevet ut i tekstområdet nedenfor.
package tekst;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
public class RegexTestHarness extends JFrame
{
private JTextField input1, input2;
private JTextArea utskrift;
private String reguttr, teststreng;
private Pattern pattern;
private Matcher matcher;
public RegexTestHarness()
{
input1 = new JTextField( 20 );
input2 = new JTextField( 30 );
utskrift = new JTextArea( 10, 40 );
utskrift.setEditable( false );
Container c = getContentPane();
c.setLayout(new FlowLayout());
c.add(new JLabel("Regulært uttrykk det skal testes på"));
c.add(input1);
c.add(new JLabel("Uttrykk det skal søkes i"));
c.add(input2);
c.add(new JScrollPane(utskrift));
Inputlytter lytter = new Inputlytter();
input1.addActionListener(lytter);
input2.addActionListener(lytter);
setSize( 500, 300 );
setVisible(true);
}
public static void main(String[] args)
{
new RegexTestHarness();
}
private class Inputlytter implements ActionListener
{
public void actionPerformed( ActionEvent e )
{
if ( e.getSource() == input1 )
{
try
{
reguttr = input1.getText();
pattern = Pattern.compile(reguttr);
utskrift.setText("");
}
catch ( PatternSyntaxException pe )
{
utskrift.setText("Syntaksfeil i regulært uttrykk!" );
}
}
else if ( e.getSource() == input2 )
{
try
{
teststreng = input2.getText();
matcher = pattern.matcher(teststreng);
boolean found = false;
while (matcher.find())
{
utskrift.append("Jeg fant tekstkomponenten "
+ matcher.group()
+ " med start i indeks " + matcher.start()
+ " og slutt i indeks " + matcher.end() + "\n");
found = true;
}
if(!found)
{
utskrift.append("Fant ikke noe som matchet.\n");
}
}
catch ( NullPointerException ex)
{
utskrift.setText("Mangler mønster til å sammenlikne med!");
}
}
}
}
}
Copyright © Kjetil Grønning, 2007