Comment puis-je parcourir les points de code Unicode d'une chaîne Java?

105

Donc, je sais String#codePointAt(int), mais il est indexé par le chardécalage, pas par le décalage du point de code.

Je pense essayer quelque chose comme:

Mais mes inquiétudes sont

  • Je ne sais pas si les points de code qui sont naturellement dans la plage des substituts élevés seront stockés sous forme de deux charvaleurs ou une
  • cela semble être un moyen extrêmement coûteux d'itérer les personnages
  • quelqu'un a dû trouver quelque chose de mieux.
rampion
la source

Réponses:

143

Oui, Java utilise un encodage UTF-16-esque pour les représentations internes des chaînes et, oui, il encode les caractères en dehors du plan multilingue de base ( BMP ) en utilisant le schéma de substitution.

Si vous savez que vous aurez affaire à des caractères en dehors du BMP, voici la manière canonique d'itérer sur les caractères d'une chaîne 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);
}
Jonathan Feinberg
la source
2
Quant à savoir si c'est "cher" ou non, eh bien ... il n'y a pas d'autre moyen intégré à Java. Mais si vous avez uniquement affaire à des scripts latins / européens / cyrilliques / grecs / hébreux / arabes, alors vous n'avez qu'à s.charAt () à votre guise. :)
Jonathan Feinberg
24
Mais tu ne devrais pas. Par exemple, si votre programme produit du XML et si quelqu'un lui donne un opérateur mathématique obscur, votre XML peut soudainement être invalide.
Escargot mécanique
2
J'aurais utilisé offset = s.offsetByCodePoints(offset, 1);. Y a-t-il un avantage à utiliser à la offset += Character.charCount(codepoint);place?
Paul Groke
3
@Mechanicalsnail Je ne comprends pas votre commentaire. Pourquoi la sortie de XML entraînerait-elle un mauvais comportement de cette réponse?
Gili
3
@Gili, la réponse est bonne. Il faisait référence au commentaire de @Jonathan Feinberg dans lequel il préconise d'utiliser charAt()ce qui est une mauvaise idée
RecursiveExceptionException
72

Java 8 ajouté CharSequence#codePointsqui renvoie un IntStreamcontenant les points de code. Vous pouvez utiliser le flux directement pour les parcourir:

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

ou avec une boucle for en collectant le flux dans un tableau:

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

Ces moyens sont probablement plus chers que la solution de Jonathan Feinbergs , mais ils sont plus rapides à lire / écrire et la différence de performances sera généralement insignifiante.

Alex - GlassEditor.com
la source
3
for (int c : (Iterable<Integer>) () -> string.codePoints().iterator())fonctionne également.
saka1029
2
Version légèrement plus courte de @ saka1029: s code:for (int c : (Iterable<Integer>) string.codePoints()::iterator) ...
Lii
7

L'itération sur les points de code est déposée en tant que demande de fonctionnalité chez Sun.

Voir Sun Bug Entry

Il existe également un exemple sur la façon d'itérer sur des String CodePoints.

Alexander Egger
la source
6
Java 8 a maintenant une méthode codePoints () intégrée à String: docs.oracle.com/javase/8/docs/api/java/lang
Dov Wasserman
7

Je pensais que j'ajouterais une méthode de contournement qui fonctionne avec les boucles foreach ( ref ), plus vous pouvez la convertir en la nouvelle chaîne # codePoints de java 8 facilement en méthode de java 8 lorsque vous passez à java 8:

Vous pouvez l'utiliser avec foreach comme ceci:

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

Voici le mthod d'aide:

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();
        }
      };
    }
  };
}

Ou bien si vous souhaitez simplement convertir une chaîne en un tableau d'int (qui peut utiliser plus de RAM que l'approche ci-dessus):

 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;
  }

Utilise heureusement "codePoints" gère en toute sécurité la paire de substitution de UTF-16 (représentation de chaîne interne de Java).

rogerdpack
la source