¿Qué es un "par sustituto" en Java?

149

Estaba leyendo la documentación para StringBuffer, en particular, el método reverse () . Esa documentación menciona algo sobre pares sustitutos . ¿Qué es un par sustituto en este contexto? ¿Y qué son los sustitutos bajos y altos ?

Raymond
fuente
3
Es la terminología UTF-16, explicada aquí: download.oracle.com/javase/6/docs/api/java/lang/…
wkl
1
Ese método tiene errores: debe invertir los caracteres completos ᴀᴋᴀ puntos de código, no partes separadas de ellos, units unidades de código. El error es que ese método heredado en particular funciona solo en unidades de caracteres individuales en lugar de en puntos de código, que es de lo que quiere String estar compuesto, no solo de unidades de caracteres. Lástima que Java no te permita usar OO para arreglar eso, pero tanto la Stringclase como las StringBufferclases han sido finalmodificadas. ¿No es un eufemismo por matar? :)
tchrist 05 de
2
@tchrist La documentación (y la fuente) dice que se invierte como una cadena de puntos de código. (Presumiblemente, 1.0.2 no hizo eso, y nunca obtendrías un cambio de comportamiento en estos días.)
Tom Hawtin - tackline

Respuestas:

127

El término "par sustituto" se refiere a un medio de codificación de caracteres Unicode con puntos de código altos en el esquema de codificación UTF-16.

En la codificación de caracteres Unicode, los caracteres se asignan a valores entre 0x0 y 0x10FFFF.

Internamente, Java utiliza el esquema de codificación UTF-16 para almacenar cadenas de texto Unicode. En UTF-16, se utilizan unidades de código de 16 bits (dos bytes). Dado que 16 bits solo pueden contener el rango de caracteres de 0x0 a 0xFFFF, se utiliza cierta complejidad adicional para almacenar valores por encima de este rango (0x10000 a 0x10FFFF). Esto se hace usando pares de unidades de código conocidas como sustitutos.

Las unidades de código sustituto se encuentran en dos rangos conocidos como "sustitutos altos" y "sustitutos bajos", dependiendo de si están permitidos al comienzo o al final de la secuencia de unidades de dos códigos.

Jeffrey L Whitledge
fuente
44
esto tiene la mayoría de los votos, pero no proporciona un solo ejemplo de código. Tampoco tiene ninguna de estas respuestas sobre cómo usarlo. Es por eso que esto está siendo rechazado.
George Xavier
57

Las primeras versiones de Java representaban caracteres Unicode utilizando el tipo de datos char de 16 bits. Este diseño tenía sentido en ese momento, porque todos los caracteres Unicode tenían valores inferiores a 65.535 (0xFFFF) y podían representarse en 16 bits. Más tarde, sin embargo, Unicode aumentó el valor máximo a 1,114,111 (0x10FFFF). Como los valores de 16 bits eran demasiado pequeños para representar todos los caracteres Unicode en la versión 3.1 de Unicode, se adoptaron valores de 32 bits, llamados puntos de código, para el esquema de codificación UTF-32. Pero los valores de 16 bits son preferibles a los valores de 32 bits para un uso eficiente de la memoria, por lo que Unicode introdujo un nuevo diseño para permitir el uso continuo de los valores de 16 bits. Este diseño, adoptado en el esquema de codificación UTF-16, asigna 1.024 valores a sustitutos altos de 16 bits (en el rango U + D800 a U + DBFF) y otros 1.024 valores a sustitutos bajos de 16 bits (en el rango U + DC00 a U + DFFF).

ibrahem shabban
fuente
77
Me gusta más que la respuesta aceptada, ya que explica cómo Unicode 3.1 reservó los valores 1024 + 1024 (alto + bajo) del original 65535 para obtener 1024 * 1024 nuevos valores, sin requisitos adicionales de que los analizadores comiencen al comienzo de un cuerda.
Eric Hirst
1
No me gusta esta respuesta por implicar que UTF-16 es la codificación Unicode más eficiente en memoria. UTF-8 existe y no representa la mayoría del texto como dos bytes. UTF-16 se usa principalmente hoy porque Microsoft lo eligió antes de que UTF-32 fuera una cosa, no por la eficiencia de la memoria. Casi la única vez que realmente querría UTF-16 es cuando está manejando muchos archivos en Windows y, por lo tanto, lo lee y lo escribe mucho. De lo contrario, UTF-32 para alta velocidad (compensaciones constantes de b / c) o UTF-8 para poca memoria (mínimo de b / c de 1 byte)
Financia la demanda de Monica el
23

Lo que dice la documentación es que las cadenas UTF-16 no válidas pueden volverse válidas después de llamar al reversemétodo, ya que pueden ser las reversas de las cadenas válidas. Un par sustituto (discutido aquí ) es un par de valores de 16 bits en UTF-16 que codifican un único punto de código Unicode; los sustitutos bajos y altos son las dos mitades de esa codificación.

Jeremiah Willcock
fuente
66
Aclaración. Una cadena debe invertirse en caracteres "verdaderos" (también conocidos como "grafemas" o "elementos de texto"). Un solo punto de código de "carácter" podría ser uno o dos fragmentos "char" (par sustituto), y un grafema podría ser uno o más de esos puntos de código (es decir, un código de carácter base más uno o más códigos de caracteres combinados, cada uno de los cuales podría ser uno o dos fragmentos de 16 bits o "caracteres" de largo). Por lo tanto, un solo grafema podría ser tres personajes combinados cada dos "caracteres" de largo, con un total de 6 "caracteres". Los 6 "caracteres" deben mantenerse juntos, en orden (es decir, no invertidos), al invertir toda la cadena de caracteres.
Triynko
44
Por lo tanto, el tipo de datos "char" es bastante engañoso. "personaje" es un término suelto. El tipo "char" es realmente solo el tamaño del fragmento UTF16 y lo llamamos carácter debido a la rareza relativa de pares sustitutos que se producen (es decir, generalmente representa un punto de código de carácter completo), por lo que "carácter" realmente se refiere a un único punto de código unicode , pero luego, con la combinación de caracteres, puede tener una secuencia de caracteres que se muestre como un solo "carácter / grafema / elemento de texto". Esto no es ciencia de cohetes; Los conceptos son simples, pero el lenguaje es confuso.
Triynko
En el momento en que se estaba desarrollando Java, Unicode estaba en su infancia. Java existió durante aproximadamente 5 años antes de que Unicode obtuviera pares sustitutos, por lo que un carácter de 16 bits se ajustaba bastante bien en ese momento. Ahora, es mucho mejor usar UTF-8 y UTF-32 que UTF-16.
Jonathan Baldwin
23

Agregar más información a las respuestas anteriores de esta publicación.

Probado en Java-12, debería funcionar en todas las versiones de Java anteriores a 5.

Como se menciona aquí: https://stackoverflow.com/a/47505451/2987755 ,
cualquier carácter (cuyo Unicode esté por encima de U + FFFF) se representa como un par sustituto, que Java almacena como un par de valores de caracteres, es decir, el Unicode único El carácter se representa como dos caracteres Java adyacentes.
Como podemos ver en el siguiente ejemplo.
1. Longitud:

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

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

2. Igualdad:
represente "🌉" en String usando Unicode \ud83c\udf09como se muestra a continuación y verifique la igualdad.

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

Java no es compatible con UTF-32

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

3. Puede convertir caracteres Unicode en cadenas Java

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

4. String.substring () no considera caracteres suplementarios

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

Para resolver esto podemos usar String.offsetByCodePoints(int index, int codePointOffset)

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

5. Iteración cadena Unicode con BreakIterator
6. Clasificación Cuerdas con Unicode java.text.Collator
7. Carácter de toUpperCase(), toLowerCase(), los métodos no deben utilizarse, en cambio, en mayúsculas y minúsculas uso de cuerdas de especial configuración regional.
8. Character.isLetter(char ch)no admite, mejor utilizado Character.isLetter(int codePoint), para cada methodName(char ch)método en la clase de caracteres habrá un tipo de los methodName(int codePoint)cuales pueden manejar caracteres suplementarios.
9. Especificar charset en String.getBytes(), la conversión de Bytes a String, InputStreamReader,OutputStreamWriter

Ref:
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

Más información sobre el ejemplo image1 image2
Otros términos que vale la pena explorar: Normalización , BiDi

dkb
fuente
2
inició sesión especialmente para votar por esta respuesta (quiero decir, cambió la ventana de incógnito a la normal: P). La mejor explicación para un novato
N-JOY
1
¡Gracias! Me alegro de que haya ayudado, pero el autor original de la publicación merece todo el agradecimiento.
dkb
¡Grandes ejemplos! Me conecté para votar también :) Y nuevamente, me hizo pensar (nuevamente) que realmente no entiendo por qué Java mantiene vivos los errores CONOCIDOS en su código. Respeto totalmente que no quieran romper el código existente, pero vamos ... ¿cuántas horas se han perdido trabajando para solucionar estos errores? Si está roto, ¡arréglalo, maldita sea!
Franz D.
6

Pequeño prefacio

  • Unicode representa puntos de código. Cada punto de código puede codificarse en bloques de 8, 16 o 32 bits de acuerdo con el estándar Unicode.
  • Antes de la Versión 3.1, la mayoría de las veces en uso era una codificación de 8 bits, conocida como UTF-8, y una codificación de 16 bits, conocida como UCS-2 o "Juego de caracteres universal codificado en 2 octetos". UTF-8 codifica puntos Unicode como una secuencia de bloques de 1 byte, mientras que UCS-2 siempre toma 2 bytes:

    A = 41 - un bloque de 8 bits con UTF-8
    A = 0041 - un bloque de 16 bits con UCS-2
    Ω = CE A9 - dos bloques de 8 bits con UTF-8
    Ω = 03A9 - un bloque de 16 bits con UCS-2

Problema

El consorcio pensó que 16 bits serían suficientes para cubrir cualquier lenguaje legible por humanos, lo que da 2 ^ 16 = 65536 posibles valores de código. Esto fue cierto para el Plano 0, también conocido como BPM o Plano Multilingüe Básico, que incluye 55,445 de 65536 puntos de código en la actualidad. BPM cubre casi todos los idiomas humanos del mundo, incluidos los símbolos chino-japonés-coreano (CJK).

El tiempo pasó y se agregaron nuevos conjuntos de caracteres asiáticos, los símbolos chinos tomaron más de 70,000 puntos solo. Ahora, incluso hay puntos Emoji como parte del estándar 😺. Se agregaron 16 nuevos aviones "adicionales" . La sala UCS-2 no fue suficiente para cubrir algo más grande que el Plano-0.

Decisión Unicode

  1. Limite Unicode a los 17 planos × 65 536 caracteres por plano = 1 114 112 puntos máximos.
  2. Presente UTF-32, anteriormente conocido como UCS-4, para contener 32 bits para cada punto de código y cubrir todos los planos.
  3. Continúe usando UTF-8 como codificación dinámica, limite UTF-8 a un máximo de 4 bytes para cada punto de código, es decir, de 1 a 4 bytes por punto.
  4. En desuso UCS-2
  5. Crear UTF-16 basado en UCS-2. Haga que UTF-16 sea dinámico, por lo que toma 2 bytes o 4 bytes por punto. Asigne 1024 puntos U + D800 – U + DBFF, llamados High Surrogates, a UTF-16; asigne 1024 símbolos U + DC00 – U + DFFF, llamados sustitutos bajos, a UTF-16.

    Con esos cambios, BPM está cubierto con 1 bloque de 16 bits en UTF-16, mientras que todos los "caracteres suplementarios" están cubiertos con pares sustitutos que presentan 2 bloques de 16 bits cada uno, totalmente 1024x1024 = 1 048 576 puntos.

    Un sustituto alto precede a un sustituto bajo . Cualquier desviación de esta regla se considera una codificación incorrecta. Por ejemplo, un sustituto sin un par es incorrecto, un sustituto bajo de pie antes de un sustituto alto es incorrecto.

    𝄞, 'SÍMBOLO MUSICAL G CLEF', está codificado en UTF-16 como un par de sustitutos 0xD834 0xDD1E (2 por 2 bytes),
    en UTF-8 como 0xF0 0x9D 0x84 0x9E (4 por 1 byte),
    en UTF-32 como 0x0001D11E (1 por 4 bytes).

Situación actual

  • Aunque de acuerdo con el estándar, los sustitutos se asignan específicamente solo a UTF-16, históricamente algunas aplicaciones de Windows y Java usaban puntos UTF-8 y UCS-2 reservados ahora al rango de sustitutos.
    Para admitir aplicaciones heredadas con codificaciones UTF-8 / UTF-16 incorrectas , se creó un nuevo estándar WTF-8 , Wobbly Transformation Format. Es compatible con puntos sustitutos arbitrarios, como un sustituto no emparejado o una secuencia incorrecta. Hoy, algunos productos no cumplen con el estándar y tratan a UTF-8 como WTF-8.
  • La solución sustituta abrió muchos problemas de seguridad en la conversión entre diferentes codificaciones, la mayoría de ellos se manejaron bien.

Muchos detalles históricos fueron suprimidos para seguir el tema ⚖.
El último estándar Unicode se puede encontrar en http://www.unicode.org/versions/latest

Artru
fuente
3

Un par sustituto son dos 'unidades de código' en UTF-16 que conforman un 'punto de código'. La documentación de Java indica que estos 'puntos de código' seguirán siendo válidos, con sus 'unidades de código' ordenadas correctamente, después del reverso. Además, establece que dos unidades de código sustituto no emparejadas pueden invertirse y formar un par sustituto válido. Lo que significa que si hay unidades de código no emparejadas, ¡entonces existe la posibilidad de que el reverso del reverso no sea el mismo!

Tenga en cuenta, sin embargo, que la documentación no dice nada sobre Graphemes, que son múltiples puntos de código combinados. Lo que significa que e y el acento que lo acompaña todavía se puede cambiar, colocando así el acento antes de la e. Lo que significa que si hay otra vocal antes de la e, puede obtener el acento que estaba en la e.

¡Ay!

Gerard ONeill
fuente