Come posso iterare attraverso i punti di codice Unicode di una stringa Java?


105

Quindi lo so String#codePointAt(int), ma è indicizzato chardall'offset, non dall'offset del punto di codice.

Sto pensando di provare qualcosa come:

Ma le mie preoccupazioni lo sono

  • Non sono sicuro se i punti di codice che sono naturalmente nell'intervallo di surrogati alti verranno memorizzati come due charvalori o uno
  • questo sembra un modo terribilmente costoso per scorrere i personaggi
  • qualcuno deve aver escogitato qualcosa di meglio.

Risposte:


143

Sì, Java utilizza una codifica UTF-16 per le rappresentazioni interne delle stringhe e, sì, codifica i caratteri al di fuori del Basic Multilingual Plane ( BMP ) utilizzando lo schema di maternità surrogata.

Se sai che avrai a che fare con caratteri al di fuori del BMP, ecco il modo canonico per iterare sui caratteri di una stringa Java:

final int length = s.length();
for (int offset = 0; offset < length; ) {
   final int codepoint = s.codePointAt(offset);

   // do something with the codepoint

   offset += Character.charCount(codepoint);
}

2
Quanto al fatto che sia "costoso" o meno, beh ... non c'è altro modo integrato in Java. Ma se hai a che fare solo con caratteri latini / europei / cirillici / greci / ebraici / arabi, allora devi solo usare caratteri () a tuo piacimento. :)
Jonathan Feinberg

24
Ma non dovresti. Ad esempio, se il tuo programma emette XML e se qualcuno gli fornisce un oscuro operatore matematico, improvvisamente il tuo XML potrebbe non essere valido.
Lumaca meccanica

2
Avrei usato offset = s.offsetByCodePoints(offset, 1);. C'è qualche vantaggio nell'usare offset += Character.charCount(codepoint);invece?
Paul Groke

3
@Mechanicalsnail non capisco il tuo commento. Perché l'output XML causa un comportamento anomalo di questa risposta?
Gili

3
@Gili la risposta va bene. Si riferiva al commento di @Jonathan Feinberg in cui sostiene l'uso charAt()che è una cattiva idea
RecursiveExceptionException

72

Aggiunto Java 8 CharSequence#codePointsche restituisce un IntStreamcontenente i punti di codice. Puoi utilizzare lo stream direttamente per scorrere su di essi:

string.codePoints().forEach(c -> ...);

o con un ciclo for raccogliendo il flusso in un array:

for(int c : string.codePoints().toArray()){
    ...
}

Questi metodi sono probabilmente più costosi rispetto alla soluzione di Jonathan Feinbergs , ma sono più veloci da leggere / scrivere e la differenza di prestazioni sarà generalmente insignificante.


3
for (int c : (Iterable<Integer>) () -> string.codePoints().iterator())funziona anche.
saka1029

2
Versione leggermente più breve del codice @ saka1029: s:for (int c : (Iterable<Integer>) string.codePoints()::iterator) ...
Lii


7

Ho pensato di aggiungere un metodo alternativo che funziona con i cicli foreach ( ref ), inoltre puoi convertirlo facilmente nel nuovo metodo String # codePoints di java 8 quando passi a java 8:

Puoi usarlo con foreach in questo modo:

 for(int codePoint : codePoints(myString)) {
   ....
 }

Ecco l'helper mthod:

public static Iterable<Integer> codePoints(final String string) {
  return new Iterable<Integer>() {
    public Iterator<Integer> iterator() {
      return new Iterator<Integer>() {
        int nextIndex = 0;
        public boolean hasNext() {
          return nextIndex < string.length();
        }
        public Integer next() {
          int result = string.codePointAt(nextIndex);
          nextIndex += Character.charCount(result);
          return result;
        }
        public void remove() {
          throw new UnsupportedOperationException();
        }
      };
    }
  };
}

O in alternativa, se vuoi solo convertire una stringa in un array di int (che potrebbe utilizzare più RAM rispetto all'approccio precedente):

 public static List<Integer> stringToCodePoints(String in) {
    if( in == null)
      throw new NullPointerException("got null");
    List<Integer> out = new ArrayList<Integer>();
    final int length = in.length();
    for (int offset = 0; offset < length; ) {
      final int codepoint = in.codePointAt(offset);
      out.add(codepoint);
      offset += Character.charCount(codepoint);
    }
    return out;
  }

Per fortuna utilizza "codePoints" gestisce in modo sicuro la coppia surrogata di UTF-16 (rappresentazione di stringa interna di java).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.