2013-04-15 16 views
13

La mia domanda è abbastanza semplice ma sconcertante. Potrebbe essere che ci sia un semplice interruttore che risolve questo, ma io non sono molto esperto in Java regex ...Strange Java Unicode Regular Expression StringIndexOutOfBoundsException

String line = ""; 
line.replaceAll("(?i)(.)\\1{2,}", "$1"); 

Questo si blocca. Se rimuovo lo switch (?i), funziona. I tre caratteri unicode non sono casuali, sono stati trovati in mezzo a un grande testo coreano, ma non so che siano validi o meno.

La cosa strana è che la regex funziona per tutto il testo tranne questo. Perché ottengo l'errore?

questa è l'eccezione ottengo

Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 6 
    at java.lang.String.charAt(String.java:658) 
    at java.lang.Character.codePointAt(Character.java:4668) 
    at java.util.regex.Pattern$CIBackRef.match(Pattern.java:4846) 
    at java.util.regex.Pattern$Curly.match(Pattern.java:4125) 
    at java.util.regex.Pattern$GroupTail.match(Pattern.java:4615) 
    at java.util.regex.Pattern$CharProperty.match(Pattern.java:3694) 
    at java.util.regex.Pattern$GroupHead.match(Pattern.java:4556) 
    at java.util.regex.Pattern$Start.match(Pattern.java:3408) 
    at java.util.regex.Matcher.search(Matcher.java:1199) 
    at java.util.regex.Matcher.find(Matcher.java:592) 
    at java.util.regex.Matcher.replaceAll(Matcher.java:902) 
    at java.lang.String.replaceAll(String.java:2162) 
    at tokenizer.Test.main(Test.java:51) 
+0

Vedere: [Java Regex Tutorial] (http://www.vogella.com/articles/JavaRegularExpressions/article.html) – Justin

+1

Come si blocca? C'è qualche eccezione? – Thilo

+0

Si prega di definire cosa si intende per arresto anomalo o "l'errore"? Non ci hai mostrato alcun errore. – eis

risposta

1

Ciò che è spiegato da Santosh in this answer non è corretto. Questo può essere dimostrato eseguendo

String str = ""; 
System.out.println("code point: " + .codePointAt(0)); 

che sarà in uscita (almeno per me) il valore 128149, che è confermato da this page come corrette. Quindi Java non interpreta la stringa in modo errato. Ha interpretato male quando si utilizza il metodo getBytes().

Tuttavia, come spiegato da OP, sembra che l'espressione regolare si arresti in modo anomalo. Non ho altra spiegazione per questo dato che si tratta di un bug in Java. In alternativa, o in alternativa, non supporta UTF-16 in base alla progettazione.

Edit:

basa su this answer:

viti compilatore

la regex sul UTF-16. Anche in questo caso, questo non può mai essere risolto o cambierà i vecchi programmi. Non è possibile aggirare il bug utilizzando la normale soluzione alternativa ai problemi Java Unicode-in-source-code compilando con java -encoding UTF-8, perché la stupida cosa memorizza le stringhe come brutto UTF-16, che necessariamente rompe in classi di personaggi. OOPS!

Sembrerebbe che questa sia una limitazione delle espressioni regolari in java.


Dal momento che si ha commentato che

Sarebbe meglio se potessi semplicemente ignorare i caratteri UTF-16 e applicare l'espressione regolare, piuttosto che un'eccezione.

Questo può certamente essere fatto. Un modo semplice è di applicare la tua espressione regolare a un determinato intervallo. Il filtraggio degli intervalli di caratteri unicode è stato spiegato in this answer. Sulla base di tale risposta, ad esempio che non sembra di soffocare, ma solo lascia i personaggi problema da solo:

line.replaceAll("(?Ui)([\\u0000-\\uffff])\\1{2,}", "$1")  

// "" -> "" 
// "foo foo" -> "foo foo" 
// "foo aAa foo" -> "foo a foo" 
+0

line.replaceAll ("(? Ui) ([\\ u0000 - \\ uffff]) \\ 1 {2,}", "$ 1"); Questa sembra essere la strada da percorrere, e scavalcare il bug. Grazie. – binit

+0

@binit nessun problema. In realtà, come informazioni aggiuntive, [questo collegamento] (http://www.oracle.com/technetwork/articles/javase/supplementary-142654.html) dice che l'espressione regolare di java dovrebbe essere in grado di gestire i caratteri supplementari, quindi penso che questo conferma che hai a che fare con un bug. – eis

4

I caratteri che hai citato sono in realtà "Double byte characters". Ciò significa che due byte formano un carattere. Tuttavia, per interpretare ciò in Java, le informazioni di codifica (quando è diversa dalla codifica di piattaforma predefinita) devono essere passate esplicitamente (altrimenti verrà utilizzata la codifica di piattaforma predefinita).

Per dimostrare questo, prendere in considerazione in seguito

String line = ""; 
System.out.println(line.length()); 

questo stampa la lunghezza come 6! Mentre abbiamo solo tre personaggi,

ora il seguente codice

String line1 = new String("".getBytes(),"UTF-8"); 
System.out.println(line1.length()); 

stampe lunghezza 3 che intendeva.

se si sostituisce la linea

String line = ""; 

con

String line1 = new String("".getBytes(),"UTF-8"); 

funziona e regex non fallisce. Ho usato UTF-8 qui. Si prega di utilizzare la codifica appropriata della piattaforma prevista.

Le librerie di espressioni regolari Java dipendono fortemente da Character Sequence che a sua volta dipende dallo schema di codifica. Per le stringhe che hanno codifiche di caratteri diverse dalla codifica predefinita, i caratteri non possono essere decodificati correttamente (mostra 6 caratteri invece di 3!) E quindi la regex ha esito negativo.

+0

Ehi Santosh, la tua correzione non funziona alla mia fine. Ho provato: nuova stringa ("". GetBytes(), "UTF-8"). ReplaceAll ("(? I) (.) \\ 1 {2,}", "$ 1"); e si blocca ancora ... anche nuovo String ("". GetBytes(), "UTF-8"). Length() mi mostra 6 (hai menzionato 3)! – binit

+0

Sulla mia macchina (Win XP SP2, jdk1.6.0_14) mostra 3 caratteri. Qual è il sistema operativo/JDK che stai utilizzando? Puoi provare qualche codifica diversa (e.q UTF-16)? Qual è il set di caratteri predefinito della tua macchina? – Santosh

+0

'line1.length()' può essere '3' solo se la codifica predefinita della piattaforma non supporta i caratteri e quindi codifica'? 'Al posto di essi. Quindi stai vedendo la lunghezza della stringa '" ??? "', non so come sia inteso. Se la codifica della piattaforma è 'UTF-8', avrai inutili round-trip. – Esailija

0

In realtà, è solo un bug.

Ecco a cosa servono le tracce di stack e l'open source.

Quando CIBackRef (per riferimento posteriore senza distinzione tra maiuscole e minuscole) viene confrontato con il gruppo, non esegue il bump dell'indice di loop correttamente. Questo dimostra la correzione:

 // Check each new char to make sure it matches what the group 
     // referenced matched last time around 
     int x = i; 
     for (int index=0; index<groupSize;) { 
      int c1 = Character.codePointAt(seq, x); 
      int c2 = Character.codePointAt(seq, j); 
      if (c1 != c2) { 
       if (doUnicodeCase) { 
        int cc1 = Character.toUpperCase(c1); 
        int cc2 = Character.toUpperCase(c2); 
        if (cc1 != cc2 && 
         Character.toLowerCase(cc1) != 
         Character.toLowerCase(cc2)) 
         return false; 
       } else { 
        if (ASCII.toLower(c1) != ASCII.toLower(c2)) 
         return false; 
       } 
      } 
      int n = Character.charCount(c1); 
      x += n; 
      index += n; // was index++ 
      j += Character.charCount(c2); 
     } 

groupSize è la charCount totale del gruppo. j è l'indice per il gruppo di riferimento.

Il test

//9ff0 9592 9ff0 9592 9ff0 9592 
    val line = "\ud83d\udc95\ud83d\udc95\ud83d\udc95" 
    Console println Try(line.replaceAll("(?ui)(.)\\1{2,}", "$1")) 

fallisce normalmente

[email protected]:~/tmp$ skalac kcharex.scala ; skala kcharex.Test 
Failure(java.lang.StringIndexOutOfBoundsException: String index out of range: 6) 

ma riesce con la correzione

[email protected]:~/tmp$ skala -J-Xbootclasspath/p:../bootfix kcharex.Test 
Success() 

L'altro bug nel codice di esempio originale è che le bandiere in linea dovrebbero comprendere ?ui . La javadoc sul Pattern.CASE_INSENSITIVE dice:

Per impostazione predefinita, la corrispondenza case-insensitive assume che solo i caratteri in il set di caratteri US-ASCII vengono abbinati. È possibile abilitare la corrispondenza senza distinzione tra maiuscole e minuscole Unicode specificando il flag UNICODE_CASE nella congiunzione con questo flag.

Come si può vedere dal frammento di codice, senza u, fallirà solo se ASCII.toLower non risultano uguali, che non è destinato. Non sono abbastanza sofisticato per sapere di un personaggio supplementare che fallirebbe quel test senza scrivere codice per capirlo.

Problemi correlati