Qu'est-ce qu'une «paire de substitution» en Java?

149

Je lisais la documentation pour StringBuffer, en particulier la méthode reverse () . Cette documentation mentionne quelque chose sur les paires de substitution . Qu'est-ce qu'une paire de substitution dans ce contexte? Et que sont les substituts bas et élevés ?

Raymond
la source
3
C'est la terminologie UTF-16, expliquée ici: download.oracle.com/javase/6/docs/api/java/lang/…
wkl
1
Cette méthode est boguée: elle devrait inverser les caractères complets ᴀᴋᴀ les points de code - pas des morceaux séparés, ᴀᴋᴀ les unités de code. Le bogue est que cette méthode héritée particulière ne fonctionne que sur des unités de caractères individuelles plutôt que sur des points de code, ce dont vous voulez que les String s soient constitués, pas seulement des unités de caractères. Dommage que Java ne vous permette pas d'utiliser OO pour résoudre ce problème, mais la Stringclasse et les StringBufferclasses ont été finaldimensionnées. Dites, n'est-ce pas un euphémisme pour tué? :)
tchrist
2
@tchrist La documentation (et la source) dit qu'elle s'inverse comme une chaîne de points de code. (Vraisemblablement 1.0.2 n'a pas fait cela, et vous n'obtiendrez jamais un tel changement de comportement ces jours-ci.)
Tom Hawtin - Tackline

Réponses:

127

Le terme «paire de substitution» fait référence à un moyen de coder des caractères Unicode avec des points de code élevés dans le schéma de codage UTF-16.

Dans le codage de caractères Unicode, les caractères sont mappés à des valeurs comprises entre 0x0 et 0x10FFFF.

En interne, Java utilise le schéma de codage UTF-16 pour stocker des chaînes de texte Unicode. En UTF-16, des unités de code 16 bits (deux octets) sont utilisées. Étant donné que 16 bits ne peuvent contenir que la plage de caractères de 0x0 à 0xFFFF, une complexité supplémentaire est utilisée pour stocker les valeurs au-dessus de cette plage (0x10000 à 0x10FFFF). Cela se fait à l'aide de paires d'unités de code appelées substituts.

Les unités de code de substitution sont dans deux plages appelées «substituts élevés» et «substituts faibles», selon qu'elles sont autorisées au début ou à la fin de la séquence à deux unités de code.

Jeffrey L Whitledge
la source
4
cela a le plus de votes mais il ne fournit pas un seul exemple de code. Aucune de ces réponses sur la manière de l’utiliser. C'est pourquoi ce vote est défavorable.
George Xavier
57

Les premières versions de Java représentaient les caractères Unicode à l'aide du type de données char 16 bits. Cette conception avait du sens à l'époque, car tous les caractères Unicode avaient des valeurs inférieures à 65 535 (0xFFFF) et pouvaient être représentés en 16 bits. Plus tard, cependant, Unicode a augmenté la valeur maximale à 1 114 111 (0 x 10FFFF). Parce que les valeurs 16 bits étaient trop petites pour représenter tous les caractères Unicode dans Unicode version 3.1, des valeurs 32 bits - appelées points de code - ont été adoptées pour le schéma de codage UTF-32. Mais les valeurs 16 bits sont préférées aux valeurs 32 bits pour une utilisation efficace de la mémoire, c'est pourquoi Unicode a introduit une nouvelle conception pour permettre l'utilisation continue des valeurs 16 bits. Cette conception, adoptée dans le schéma de codage UTF-16, attribue 1024 valeurs à des substituts élevés 16 bits (dans la plage U + D800 à U + DBFF) et 1024 autres valeurs à des substituts faibles 16 bits (dans la plage U + DC00 à U + DFFF).

ibrahem shabban
la source
7
J'aime mieux cela que la réponse acceptée, car cela explique comment Unicode 3.1 a réservé 1024 + 1024 (haut + bas) valeurs sur les 65535 d'origine pour gagner 1024 * 1024 nouvelles valeurs, sans aucune exigence supplémentaire que les analyseurs commencent au début d'un chaîne.
Eric Hirst
1
Je n'aime pas cette réponse pour impliquer que UTF-16 est l'encodage Unicode le plus efficace en mémoire. UTF-8 existe et ne rend pas la plupart du texte sur deux octets. UTF-16 est principalement utilisé aujourd'hui parce que Microsoft l'a choisi avant que UTF-32 ne soit une chose, pas pour l'efficacité de la mémoire. La seule fois que vous voulez réellement voulez UTF-16 est quand vous faites beaucoup de la gestion des fichiers sous Windows, et sont donc à la fois la lecture et l'écrire beaucoup. Sinon, UTF-32 pour haute vitesse (décalages constants b / c) ou UTF-8 pour mémoire faible (b / c minimum 1 octet)
Fund Monica's Lawsuit
23

Ce que dit cette documentation, c'est que les chaînes UTF-16 non valides peuvent devenir valides après l'appel de la reverseméthode car elles peuvent être les inversions de chaînes valides. Une paire de substitution (discutée ici ) est une paire de valeurs 16 bits en UTF-16 qui codent un seul point de code Unicode; les substituts bas et haut sont les deux moitiés de ce codage.

Jérémie Willcock
la source
6
Clarification. Une chaîne doit être inversée sur les "vrais" caractères (aka "graphèmes" ou "éléments de texte"). Un seul point de code "caractère" pourrait être un ou deux morceaux "char" (paire de substitution), et un graphème pourrait être un ou plusieurs de ces points de code (c'est-à-dire un code de caractère de base plus un ou plusieurs codes de caractères combinés, chacun desquels pourrait être un ou deux morceaux de 16 bits ou "caractères" longs). Ainsi, un seul graphème peut être composé de trois caractères combinant chacun deux "caractères" longs, totalisant 6 "caractères". Les 6 "caractères" doivent être conservés ensemble, dans l'ordre (c'est-à-dire non inversés), lors de l'inversion de la chaîne entière de caractères.
Triynko
4
Par conséquent, le type de données "char" est plutôt trompeur. «caractère» est un terme vague. Le type "char" est en réalité juste la taille de bloc UTF16 et nous l'appelons caractère en raison de la rareté relative des paires de substitution qui se produisent (c'est-à-dire qu'il représente généralement un point de code de caractère entier), donc "caractère" fait vraiment référence à un seul point de code unicode , mais avec les caractères de combinaison, vous pouvez avoir une séquence de caractères qui s'affichent sous la forme d'un seul "caractère / graphème / élément de texte". C'est pas sorcier; les concepts sont simples, mais le langage est déroutant.
Triynko
Au moment du développement de Java, Unicode en était à ses balbutiements. Java existait pendant environ 5 ans avant qu'Unicode n'obtienne des paires de substitution, donc un caractère 16 bits convenait plutôt bien à l'époque. Maintenant, il vaut mieux utiliser UTF-8 et UTF-32 que UTF-16.
Jonathan Baldwin
23

Ajout d'informations supplémentaires aux réponses ci-dessus à partir de ce post.

Testé en Java-12, devrait fonctionner dans toutes les versions Java supérieures à 5.

Comme mentionné ici: https://stackoverflow.com/a/47505451/2987755 ,
quel que soit le caractère (dont Unicode est au-dessus de U + FFFF) est représenté comme une paire de substitution, que Java stocke comme une paire de valeurs de caractères, c'est-à-dire l'unique Unicode Le caractère est représenté par deux caractères Java adjacents.
Comme nous pouvons le voir dans l'exemple suivant.
1. Longueur:

"🌉".length()  //2, Expectations was it should return 1

"🌉".codePointCount(0,"🌉".length())  //1, To get the number of Unicode characters in a Java String  

2. Égalité:
Représentez "🌉" en chaîne en utilisant Unicode \ud83c\udf09comme ci-dessous et vérifiez l'égalité.

"🌉".equals("\ud83c\udf09") // true

Java ne prend pas en charge UTF-32

"🌉".equals("\u1F309") // false  

3. Vous pouvez convertir le caractère Unicode en chaîne Java

"🌉".equals(new String(Character.toChars(0x0001F309))) //true

4. String.substring () ne prend pas en compte les caractères supplémentaires

"🌉🌐".substring(0,1) //"?"
"🌉🌐".substring(0,2) //"🌉"
"🌉🌐".substring(0,4) //"🌉🌐"

Pour résoudre ce problème, nous pouvons utiliser String.offsetByCodePoints(int index, int codePointOffset)

"🌉🌐".substring(0,"🌉🌐".offsetByCodePoints(0,1) // "🌉"
"🌉🌐".substring(2,"🌉🌐".offsetByCodePoints(1,2)) // "🌐"

5. Faire Itère Unicode avec BreakIterator
6. Tri des chaînes avec Unicode java.text.Collator
7. de caractère toUpperCase(), les toLowerCase()méthodes ne doivent pas être utilisés, à la place, en majuscules l' utilisation de chaînes et minuscules des paramètres régionaux spécifiques.
8. Character.isLetter(char ch)ne prend pas en charge, mieux utilisé Character.isLetter(int codePoint), pour chaque methodName(char ch)méthode de la classe Character dont le type methodName(int codePoint)peut gérer les caractères supplémentaires.
9. Indiquez charset dans String.getBytes(), la conversion d'octets à cordes InputStreamReader,OutputStreamWriter

Réf:
https://coolsymbol.com/emojis/emoji-for-copy-and-paste.html#objects
https://www.online-toolz.com/tools/text-unicode-entities-convertor.php
https: //www.ibm.com/developerworks/library/j-unicode/index.html
https://www.oracle.com/technetwork/articles/javaee/supplementary-142654.html

Plus d'informations sur l'exemple image1 image2
Autres termes à explorer: Normalisation , BiDi

dkb
la source
2
connecté spécialement pour voter pour cette réponse (je veux dire changé la fenêtre de navigation privée à normale: P). Meilleure explication pour un noob
N-JOY
1
Merci !, Je suis heureux que cela ait aidé, mais l'auteur de l'article original mérite toute notre appréciation.
dkb
Grands exemples! Je me suis connecté pour voter aussi :) Et encore une fois, cela m'a fait penser (encore) que je ne comprends vraiment pas pourquoi Java maintient les bogues CONNUS vivants dans leur code. Je respecte totalement qu'ils ne veulent pas casser le code existant, mais allez ... combien d'heures ont été perdues pour contourner ces bogues? S'il est cassé, réparez-le, bon sang!
Franz D.
6

Petite préface

  • Unicode représente les points de code. Chaque point de code peut être codé en blocs de 8, 16 ou 32 bits selon la norme Unicode.
  • Avant la version 3.1, le codage 8 bits, connu sous le nom UTF-8, était principalement utilisé et le codage 16 bits, appelé UCS-2 ou «Universal Character Set codé en 2 octets». UTF-8 encode les points Unicode comme une séquence de blocs de 1 octet, tandis que UCS-2 prend toujours 2 octets:

    A = 41 - un bloc de 8 bits avec UTF-8
    A = 0041 - un bloc de 16 bits avec UCS-2
    Ω = CE A9 - deux blocs de 8 bits avec UTF-8
    Ω = 03A9 - un bloc de 16 bits avec UCS-2

Problème

Le consortium a pensé que 16 bits suffiraient pour couvrir n'importe quel langage lisible par l'homme, ce qui donne 2 ^ 16 = 65536 valeurs de code possibles. Cela était vrai pour l'avion 0, également connu sous le nom de BPM ou Basic Multilingual Plane, qui comprend aujourd'hui 55 445 points de code sur 65 536. BPM couvre presque toutes les langues humaines dans le monde, y compris les symboles chinois-japonais-coréen (CJK).

Le temps a passé et de nouveaux jeux de caractères asiatiques ont été ajoutés, les symboles chinois ont pris plus de 70 000 points seuls. Maintenant, il y a même des points Emoji dans le cadre de la norme 😺. De nouveaux avions "supplémentaires" ont été ajoutés. La salle UCS-2 n'était pas suffisante pour couvrir quelque chose de plus grand que Plane-0.

Décision Unicode

  1. Limiter Unicode aux 17 plans × 65 536 caractères par plan = 1 114 112 points maximum.
  2. Présentez UTF-32, anciennement connu sous le nom UCS-4, pour contenir 32 bits pour chaque point de code et couvrir tous les plans.
  3. Continuez à utiliser UTF-8 comme codage dynamique, limitez UTF-8 à 4 octets maximum pour chaque point de code, c'est-à-dire de 1 à 4 octets par point.
  4. Obsolète UCS-2
  5. Créez UTF-16 basé sur UCS-2. Rendre UTF-16 dynamique, donc il prend 2 octets ou 4 octets par point. Attribuez 1 024 points U + D800 – U + DBFF, appelés High Surrogates, à UTF-16; affectez 1024 symboles U + DC00 – U + DFFF, appelés Low Surrogates, à UTF-16.

    Avec ces changements, le BPM est couvert avec 1 bloc de 16 bits en UTF-16, tandis que tous les «caractères supplémentaires» sont recouverts de paires de substitution présentant 2 blocs de 16 bits chacun, au total 1024x1024 = 1 048 576 points.

    Un substitut élevé précède un substitut faible . Tout écart par rapport à cette règle est considéré comme un mauvais encodage. Par exemple, un substitut sans paire est incorrect, un substitut bas devant un substitut élevé est incorrect.

    𝄞, 'MUSICAL SYMBOL G CLEF', est codé en UTF-16 comme une paire de substituts 0xD834 0xDD1E (2 par 2 octets),
    en UTF-8 comme 0xF0 0x9D 0x84 0x9E (4 par 1 octet),
    en UTF-32 comme 0x0001D11E (1 sur 4 octets).

Situation actuelle

  • Bien que selon la norme, les substituts ne soient spécifiquement attribués qu'à UTF-16, historiquement, certaines applications Windows et Java utilisaient des points UTF-8 et UCS-2 réservés maintenant à la plage de substitution.
    Pour prendre en charge les applications héritées avec des encodages UTF-8 / UTF-16 incorrects, un nouveau WTF-8 standard , Wobbly Transformation Format, a été créé. Il prend en charge les points de substitution arbitraires, tels qu'un substitut non apparié ou une séquence incorrecte. Aujourd'hui, certains produits ne sont pas conformes à la norme et traitent UTF-8 comme WTF-8.
  • La solution de substitution a ouvert de nombreux problèmes de sécurité lors de la conversion entre différents encodages, la plupart d'entre eux étant bien gérés.

De nombreux détails historiques ont été supprimés pour suivre le sujet ⚖.
La dernière norme Unicode est disponible sur http://www.unicode.org/versions/latest

Artru
la source
3

Une paire de substitution est constituée de deux «unités de code» en UTF-16 qui constituent un «point de code». La documentation Java indique que ces «points de code» seront toujours valides, avec leurs «unités de code» correctement ordonnées, après l'inverse. Il indique en outre que deux unités de code de substitution non appariées peuvent être inversées et former une paire de substitution valide. Ce qui signifie que s'il y a des unités de code non appariées, il y a une chance que l'inverse de l'inverse ne soit pas le même!

Notez cependant que la documentation ne dit rien sur les graphèmes - qui sont plusieurs points de code combinés. Ce qui signifie que e et l'accent qui l'accompagne peuvent encore être commutés, plaçant ainsi l'accent avant le e. Ce qui signifie que s'il y a une autre voyelle avant le e, elle peut avoir l'accent qui était sur le e.

Yikes!

Gérard ONeill
la source