Avatar billede dsj Nybegynder
26. december 2003 - 14:14 Der er 18 kommentarer og
1 løsning

Problem med String.split()

Jeg har en lille kringle vedr. String.split(String regex), som jeg mangler nogle ideer til at få løst.

Det drejer som om modtagelse af data på en server fra en række klienter. Når data modtages, er der ofte tale om flere beskeder i samme læsning fra netværksforbindelsen, og beskeder adskilles fra hinanden med \0.

Det problem der skal kringles er så, at det ikke er sikkert at hele beskeder modtages, der kan også være halve. F.eks. kan serveren modtage 1½ besked hvilket vil sige, at den første, hele besked skal afvikles, men den halve gemmes, til der igen læses fra netværksforbindelsen.

Pt. anvender jeg String.split() til at opdele modtagede beskeder:
    String[] messages = data.split("\0");

Problemet er så, at modtager serveren 1½ besked, vil messages indeholde to String's, den hele og den halve besked, men \0 er fjernes altid split("\0"), således at jeg ikke kan se om den sidste besked i messages er hel eller halv.

Hvordan løser jeg dette problem? Det ville være bedst, om "\0" kom med i indholdet af messages. Løsningen må ikke være en StringTokenizer. Sun fraråder at den bruges og den vil koste for meget CPU-tid, da der hver gang data modtages skal oprettes en ny StringTokenizer - for meget garbage collection...
Avatar billede erikjacobsen Ekspert
26. december 2003 - 14:21 #1
Kan du ikke se det på antallet

  "a\0b"

giver 2 strenge, mens

"a\0b\0"

giver 3 med split (hvis jeg ellers husker korrekt)
Avatar billede arne_v Ekspert
26. december 2003 - 14:23 #2
1) Jeg har svært ved at tro på at StringTokenizer er langsommere end
  regular expressions.

2) Du ville være meget bedre stillet med en længde prefix protokol end
  med en nul termineret protokol.

3) Dit konkrete problem må kunne løses med at bruge java.util.regex
  klasserne i.s.f. bare String split.
Avatar billede arne_v Ekspert
26. december 2003 - 14:36 #3
Jeg tror faktisk at Eriks løsning er den simpleste for dit problem.

Min bliver til noget a la:

import java.util.regex.*;

public class RegexSplit {
    private static Pattern p = Pattern.compile("[^\0]*[\0]");
    public static void process(String s) {
        Matcher m = p.matcher(s);
        int ix = 0;
        while(m.find()) {
            System.out.println("Complete string: " + m.group());
            ix = m.end();
        }
        if(ix < s.length()) {
            System.out.println("Incomplete string: " + s.substring(ix));
        }
    }
    public static void main(String[] args) {
        String s1 =  "a\0b\0";
        process(s1);
        String s2 =  "a\0b";
        process(s2);
    }
}
Avatar billede dsj Nybegynder
26. december 2003 - 14:50 #4
Eriks løsning går ikke, begge eksempler vil returnere 2 strenge.

Arnes:
1) Funktionaliteten køres rigtig ofte på serveren og lidt betyder derfor meget. Det problem jeg ser er, at at en StringTokenizer skal oprettes hver gang, og oprettelse af nye objekter er netop noget der tager tid.

2) Kan ikke lade sig gøre. Protokollen bygger på XML og håndteres automatisk af i Flash-klienten.

3) Ku være jeg skulle kigge lidt på dette. For at sige det mildt er jeg ikke meget inde i dette område.
Avatar billede dsj Nybegynder
26. december 2003 - 14:57 #5
Følgende sætning fra Suns API for StringTokenizer skræmmer mig også lidt: "StringTokenizer is a legacy class that is retained for compatibility reasons although its use is discouraged in new code. It is recommended that anyone seeking this functionality use the split method of String or the java.util.regex package instead."

Jeg vil lige prøve at lege lidt med det; egentlig anvender String.split() jo blot Pattern.compile(regex).split()...
Avatar billede erikjacobsen Ekspert
26. december 2003 - 15:14 #6
Hvad er så reglen for at adskille beskeder, hvis du ikke kan gøre som
jeg foreslår? Hvordan ville det se ud med StringTokenizer hvis du kan
få det til at virke? Hvordan kan du se at beskeden kun er halv og ikke
det hele?
Avatar billede arne_v Ekspert
26. december 2003 - 15:15 #7
StringTokenizer kan man med bede om at returnere delimiters !
Avatar billede dsj Nybegynder
26. december 2003 - 15:16 #8
StringTokenizer kan returnere delimiter'en, som jeg så ville kunne kontrollere på...
Avatar billede arne_v Ekspert
26. december 2003 - 15:17 #9
Min 14:36:07 kode returnerer korrekt resultat:

Complete string: a\0
Complete string: b\0
Complete string: a\0
Incomplete string: b

men jeg tvivler på at den performer godt.
Avatar billede arne_v Ekspert
26. december 2003 - 15:30 #10
Med hensyn til performance så lavede jeg følgende lille program:

import java.util.StringTokenizer;

public class SplitTest {
    private final static int N = 10000000;
    public static void main(String[] args) {
        String s = "a\0b\0";
        long t1 = System.currentTimeMillis();
        for(int i = 0; i < N; i++) {
            String[] ss = s.split("\0");
            for(int j = 0; j < ss.length; j++) {
                String sss = ss[j];
                if(!sss.equals("a") && !sss.equals("b")) System.err.println("Oops");
            }
        }
        long t2 = System.currentTimeMillis();
        long t3 = System.currentTimeMillis();
        for(int i = 0; i < N; i++) {
            StringTokenizer st = new StringTokenizer(s, "\0");
            while(st.hasMoreTokens()) {
                String sss = st.nextToken();
                if(!sss.equals("a") && !sss.equals("b")) System.err.println("Oops");
            }
        }
        long t4 = System.currentTimeMillis();
        System.out.println("regex split = " + (t2 - t1) + " string tokenizer = " + (t4 - t3));
    }
}
Avatar billede arne_v Ekspert
26. december 2003 - 15:31 #11
På mit system gav det:

regex split = 35750 string tokenizer = 9937


d.v.s. at String split er 3.5 gange langsommere en StringTokenizer...
Avatar billede dsj Nybegynder
26. december 2003 - 15:31 #12
Hm, ja det var jo interessant nok...
Avatar billede arne_v Ekspert
26. december 2003 - 16:11 #13
import java.util.StringTokenizer;
import java.util.regex.Pattern;

public class SplitTest2 {
    private final static int N = 1000000;
    public static void main(String[] args) {
        String s = "a\0b\0";
        long t1 = System.currentTimeMillis();
        for(int i = 0; i < N; i++) {
            String[] ss = s.split("\0");
            for(int j = 0; j < ss.length; j++) {
                String sss = ss[j];
                if(!sss.equals("a") && !sss.equals("b")) System.err.println("Oops");
            }
        }
        long t2 = System.currentTimeMillis();
        long t3 = System.currentTimeMillis();
        for(int i = 0; i < N; i++) {
            StringTokenizer st = new StringTokenizer(s, "\0");
            while(st.hasMoreTokens()) {
                String sss = st.nextToken();
                if(!sss.equals("a") && !sss.equals("b")) System.err.println("Oops");
            }
        }
        long t4 = System.currentTimeMillis();
        long t5 = System.currentTimeMillis();
        Pattern p = Pattern.compile("\0");
        for(int i = 0; i < N; i++) {
            String[] ss = p.split(s);
            for(int j = 0; j < ss.length; j++) {
                String sss = ss[j];
                if(!sss.equals("a") && !sss.equals("b")) System.err.println("Oops");
            }
        }
        long t6 = System.currentTimeMillis();
        System.out.println("regex split = " + (t2 - t1) +
                            " string tokenizer = " + (t4 - t3) +
                            " smart regex split = " + (t6 - t5));
    }
}

giver:

regex split = 3594 string tokenizer = 969 smart regex split = 2500

så det hjælper lidt men stadig ikke nok.
Avatar billede dsj Nybegynder
26. december 2003 - 16:27 #14
Ku godt være man skulle overveje StringTokenizer så. Behandling af regular expressions er åbenbart ikke det mindst tidskrævende man kan sætte CPU'en til...
Avatar billede arne_v Ekspert
26. december 2003 - 16:32 #15
Regular expressions er et kraftfuyldt værktøj, men det må i sagens
natur koste lidt at bruge.

Man kan jo bare prøve at overveje hvordan man selv ville kode
regular expressions og string tokenizer. Den sidste er lige ud af landevejen.
Den første er lidt giftig.

Når det er sagt så skal man notere sig at reg exp jo er ny i 1.4 og at der
formentlig vil blive brugt tid på at optimere den i de kommende
versioner (mine tal er iøvrigt kun for 1.4.1_01 på Win32).
Avatar billede dsj Nybegynder
26. december 2003 - 23:51 #16
Arne, kan du som et svar forklare hvad "[^\0]*[\0]" gør?
Avatar billede arne_v Ekspert
26. december 2003 - 23:54 #17
nul til mange forkomster af noget der ikke er \0 efterfulgt af en forekomst af \0

(muligvis behøves [] ikke omkring den sidste)
Avatar billede dsj Nybegynder
26. december 2003 - 23:58 #18
^ betyder altså nul forekomster?
Avatar billede arne_v Ekspert
27. december 2003 - 00:00 #19
^\0 betyder alle andre tegn end \0
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Kurser inden for grundlæggende programmering

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester