O que é um "par substituto" em Java?

149

Eu estava lendo a documentação para StringBuffer, em particular o método reverse () . Essa documentação menciona algo sobre pares substitutos . O que é um par substituto nesse contexto? E o que são substitutos baixos e altos ?

Raymond
fonte
3
É a terminologia UTF-16, explicada aqui: download.oracle.com/javase/6/docs/api/java/lang/…
wkl
1
Esse método é incorreto: deve reverter caracteres completos ᴀᴋᴀ pontos de código - não separar partes deles ᴀᴋᴀ unidades de código. O problema é que esse método herdado específico funciona apenas em unidades de caracteres individuais em vez de em pontos de código, que é do que você deseja String que sejam feitos, não apenas de unidades de caracteres. Pena que o Java não permite que você use OO para corrigir isso, mas a Stringclasse e as StringBufferclasses foram finaldimensionadas. Diga, isso não é um eufemismo para mortos? :)
tchrist
2
@tchrist A documentação (e fonte) diz que é revertida como uma sequência de pontos de código. (Presumivelmente 1.0.2 não fez isso, e você nunca teria uma mudança de comportamento nos dias de hoje.)
Tom Hawtin - defina

Respostas:

128

O termo "par substituto" refere-se a um meio de codificação de caracteres Unicode com altos pontos de código no esquema de codificação UTF-16.

Na codificação de caracteres Unicode, os caracteres são mapeados para valores entre 0x0 e 0x10FFFF.

Internamente, Java usa o esquema de codificação UTF-16 para armazenar seqüências de caracteres de texto Unicode. No UTF-16, são utilizadas unidades de código de 16 bits (dois bytes). Como 16 bits podem conter apenas o intervalo de caracteres de 0x0 a 0xFFFF, alguma complexidade adicional é usada para armazenar valores acima desse intervalo (0x10000 a 0x10FFFF). Isso é feito usando pares de unidades de código conhecidas como substitutos.

As unidades de código substituto estão em dois intervalos conhecidos como "substitutos altos" e "substitutos baixos", dependendo de serem permitidos no início ou no final da sequência da unidade de dois códigos.

Jeffrey L Whitledge
fonte
4
isso tem o maior número de votos, mas não fornece um exemplo de código único. Também não temos nenhuma dessas respostas sobre como usá-lo. É por isso que isso está sendo prejudicado.
George Xavier
57

As versões anteriores do Java representavam caracteres Unicode usando o tipo de dados char de 16 bits. Esse design fazia sentido no momento, porque todos os caracteres Unicode tinham valores menores que 65.535 (0xFFFF) e podiam ser representados em 16 bits. Mais tarde, no entanto, o Unicode aumentou o valor máximo para 1.114.111 (0x10FFFF). Como os valores de 16 bits eram muito pequenos para representar todos os caracteres Unicode na versão 3.1, os valores de 32 bits - chamados pontos de código - foram adotados para o esquema de codificação UTF-32. Mas os valores de 16 bits são preferidos aos valores de 32 bits para um uso eficiente da memória; portanto, o Unicode introduziu um novo design para permitir o uso continuado dos valores de 16 bits. Esse design, adotado no esquema de codificação UTF-16, atribui 1.024 valores a substitutos altos de 16 bits (no intervalo U + D800 a U + DBFF) e outros 1.024 valores a substitutos baixos de 16 bits (no intervalo U + DC00 para U + DFFF).

ibrahem shabban
fonte
7
Gosto disso melhor do que a resposta aceita, pois explica como o Unicode 3.1 reservou valores 1024 + 1024 (alto + baixo) dos 65535 originais para obter 1024 * 1024 novos valores, sem requisitos adicionais que os analisadores iniciam no início de um corda.
Eric Hirst
1
Não gosto desta resposta por sugerir que UTF-16 é a codificação Unicode com maior eficiência de memória. O UTF-8 existe e não renderiza a maioria dos textos como dois bytes. Atualmente, o UTF-16 é usado principalmente porque a Microsoft o escolheu antes de o UTF-32 ser uma coisa, não para eficiência de memória. A única vez em que você realmente deseja o UTF-16 é quando você está lidando com muitos arquivos no Windows e, portanto, está lendo e escrevendo bastante. Caso contrário, UTF-32 para alta velocidade (compensações constantes b / c) ou UTF-8 para pouca memória (b / c no mínimo 1 byte)
Fund Monica Monica's Law
23

O que essa documentação está dizendo é que cadeias UTF-16 inválidas podem se tornar válidas após a chamada do reversemétodo, pois podem ser o inverso de cadeias válidas. Um par substituto (discutido aqui ) é um par de valores de 16 bits no UTF-16 que codifica um único ponto de código Unicode; os substitutos baixos e altos são as duas metades dessa codificação.

Jeremiah Willcock
fonte
6
Esclarecimento. Uma string deve ser revertida em caracteres "verdadeiros" (também conhecidos como "grafemas" ou "elementos de texto"). Um único ponto de código de "caractere" pode ser um ou dois blocos "char" (par substituto) e um grafema pode ser um ou mais desses pontos de código (ou seja, um código de caractere base mais um ou mais códigos de caracteres combinados, cada um dos quais pode ter um ou dois pedaços de 16 bits ou "caracteres"). Portanto, um único grafema poderia ter três caracteres combinados, cada um com dois "caracteres", totalizando 6 "caracteres". Todos os 6 "caracteres" devem ser mantidos juntos, em ordem (ou seja, não invertidos), ao inverter toda a cadeia de caracteres.
Triynko
4
Portanto, o tipo de dados "char" é bastante enganador. "caráter" é um termo solto. O tipo "char" é realmente apenas o tamanho do bloco UTF16 e chamamos de caractere devido à raridade relativa de pares substitutos que ocorrem (ou seja, geralmente representa um ponto de código de caractere completo), então "caractere" realmente se refere a um único ponto de código unicode , mas com os caracteres combinados, você pode ter uma sequência de caracteres que é exibida como um único "caractere / grafema / elemento de texto". Isso não é ciência de foguetes; os conceitos são simples, mas a linguagem é confusa.
Triynko
No momento em que o Java estava sendo desenvolvido, o Unicode ainda estava em sua infância. O Java existia cerca de 5 anos antes do Unicode obter pares substitutos, então um caractere de 16 bits se encaixava muito bem na época. Agora, você está muito melhor usando UTF-8 e UTF-32 que UTF-16.
Jonathan Baldwin
23

Adicionando mais algumas informações às respostas acima deste post.

Testado em Java-12, deve funcionar em todas as versões Java acima de 5.

Conforme mencionado aqui: https://stackoverflow.com/a/47505451/2987755 ,
o caractere (cujo Unicode está acima de U + FFFF) é representado como um par substituto, que Java armazena como um par de valores de caracteres, ou seja, o único Unicode caractere é representado como dois caracteres Java adjacentes.
Como podemos ver no exemplo a seguir.
1. comprimento:

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

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

2. Igualdade:
represente "🌉" para String usando Unicode \ud83c\udf09como abaixo e verifique a igualdade.

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

Java não suporta UTF-32

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

3. Você pode converter caracteres Unicode em Java String

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

4. String.substring () não considera caracteres suplementares

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

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

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

5. corda Iterating Unicode com BreakIterator
6. Sorting Cordas com Unicode java.text.Collator
7. do personagem toUpperCase(), toLowerCase(), métodos não deve ser usado, em vez disso, maiúscula uso de Cordas e minúsculas de particular localidade.
8. Character.isLetter(char ch)não suporta, melhor usado Character.isLetter(int codePoint), para cada methodName(char ch)método na classe Caractere, haverá um tipo methodName(int codePoint)que pode manipular caracteres suplementares.
9. Especificar conjunto de caracteres em String.getBytes(), conversão de Bytes para 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

Mais informações sobre o exemplo image1 image2
Outros termos que vale a pena explorar: Normalização , BiDi

dkb
fonte
2
conectado especialmente para votar nesta resposta (quero dizer, alterei a janela de incógnita para normal: P). Melhor explicação para um noob
N-JOY
1
Obrigado !, Fico feliz que tenha ajudado, mas o autor original do post merece toda a apreciação.
Dkb
Grandes exemplos! Fiz logon para votar novamente :) E, novamente, me fez pensar (de novo) que realmente não entendo por que o Java mantém os bugs CONHECIDOS vivos em seu código. Eu respeito totalmente que eles não querem quebrar o código existente, mas vamos lá ... quantas horas foram perdidas para solucionar esses bugs? Se estiver quebrado, conserte, caramba!
Franz D.
6

Prefácio pequeno

  • Unicode representa pontos de código. Cada ponto de código pode ser codificado em blocos de 8, 16 ou 32 bits, de acordo com o padrão Unicode.
  • Antes da versão 3.1, o uso mais comum era o encconding de 8 bits, conhecido como UTF-8, e a codificação de 16 bits, conhecida como UCS-2 ou "Conjunto Universal de Caracteres codificado em 2 octetos". UTF-8 codifica pontos Unicode como uma sequência de blocos de 1 byte, enquanto o UCS-2 sempre leva 2 bytes:

    A = 41 - um bloco de 8 bits com UTF-8
    A = 0041 - um bloco de 16 bits com UCS-2
    Ω = CE A9 - dois blocos de 8 bits com UTF-8
    Ω = 03A9 - um bloco de 16 bits com UCS-2

Problema

O consórcio achou que 16 bits seriam suficientes para cobrir qualquer linguagem legível por humanos, o que fornece 2 ^ 16 = 65536 possíveis valores de código. Isso era verdade para o Plano 0, também conhecido como BPM ou Plano Multilíngue Básico, que inclui 55.445 de 65536 pontos de código hoje. O BPM cobre quase todas as línguas humanas do mundo, incluindo símbolos chinês-japonês-coreano (CJK).

O tempo passou e novos conjuntos de caracteres asiáticos foram adicionados; os símbolos chineses ocuparam mais de 70.000 pontos. Agora, existem até pontos Emoji como parte do padrão 😺. Novos 16 aviões "adicionais" foram adicionados. A sala UCS-2 não era suficiente para cobrir algo maior que o avião 0.

Decisão Unicode

  1. Limite Unicode aos 17 planos × 65 536 caracteres por plano = 1 114 112 pontos máximos.
  2. Apresente UTF-32, anteriormente conhecido como UCS-4, para armazenar 32 bits para cada ponto de código e cobrir todos os planos.
  3. Continue a usar UTF-8 como codificação dinâmica, limite UTF-8 a 4 bytes no máximo para cada ponto de código, ou seja, de 1 a 4 bytes por ponto.
  4. Descontinuar UCS-2
  5. Crie UTF-16 com base no UCS-2. Faça o UTF-16 dinâmico, para que ele use 2 bytes ou 4 bytes por ponto. Atribua 1024 pontos U + D800 – U + DBFF, chamados Altos Substitutos, a UTF-16; atribua 1024 símbolos U + DC00 – U + DFFF, chamados substitutos baixos, a UTF-16.

    Com essas alterações, o BPM é coberto com 1 bloco de 16 bits em UTF-16, enquanto todos os "caracteres suplementares" são cobertos com pares substitutos que apresentam 2 blocos por 16 bits cada, totalmente 1024x1024 = 1.048.576 pontos.

    Um substituto alto precede um substituto baixo . Qualquer desvio desta regra é considerado como uma codificação incorreta. Por exemplo, um substituto sem um par está incorreto, um substituto baixo está em pé antes que um substituto alto esteja incorreto.

    𝄞, 'MUSICAL SYMBOL G CLEF', é codificado em UTF-16 como um par de substitutos 0xD834 0xDD1E (2 por 2 bytes),
    em UTF-8 como 0xF0 0x9D 0x84 0x9E (4 por 1 byte),
    em UTF-32 como 0x0001D11E (1 por 4 bytes).

Situação atual

  • Embora de acordo com o padrão, os substitutos sejam atribuídos especificamente apenas ao UTF-16, historicamente alguns aplicativos Windows e Java usavam pontos UTF-8 e UCS-2 reservados agora para o intervalo de substitutos.
    Para dar suporte a aplicativos legados com codificações UTF-8 / UTF-16 incorretas , foi criado um novo WTF-8 padrão , o Wobbly Transformation Format. Ele suporta pontos substitutos arbitrários, como um substituto não emparelhado ou uma sequência incorreta. Hoje, alguns produtos não estão em conformidade com o padrão e tratam o UTF-8 como WTF-8.
  • A solução substituta abriu muitos problemas de segurança na conversão entre codificações diferentes, a maioria deles foi bem tratada.

Muitos detalhes históricos foram suprimidos para seguir o tópico ⚖.
O último padrão Unicode pode ser encontrado em http://www.unicode.org/versions/latest

Artru
fonte
3

Um par substituto é duas 'unidades de código' em UTF-16 que compõem um 'ponto de código'. A documentação Java está afirmando que esses 'pontos de código' ainda serão válidos, com suas 'unidades de código' ordenadas corretamente, após o inverso. Afirma ainda que duas unidades de código substituto não emparelhadas podem ser revertidas e formar um par substituto válido. O que significa que, se houver unidades de código não emparelhadas, existe a chance de o inverso do reverso não ser o mesmo!

Observe, porém, que a documentação não diz nada sobre Graphemes - que são vários pontos de código combinados. O que significa que e o sotaque que o acompanha ainda pode ser alterado, colocando assim o sotaque antes do e. O que significa que, se houver outra vogal antes do e, poderá obter o sotaque que estava no e.

Caramba!

Gerard ONeill
fonte