Che cos'è una "coppia surrogata" in Java?


149

Stavo leggendo la documentazione per StringBuffer, in particolare il metodo reverse () . Quella documentazione menziona qualcosa sulle coppie surrogate . Cos'è una coppia surrogata in questo contesto? E cosa sono i surrogati bassi e alti ?


3
È la terminologia UTF-16, spiegata qui: download.oracle.com/javase/6/docs/api/java/lang/…
wkl

1
Questo metodo è difettoso: dovrebbe invertire i caratteri completi ᴀᴋᴀ punti di codice - non parti separate di essi, ᴀᴋᴀ unità di codice. Il bug è che quel particolare metodo legacy funziona solo su singole unità di caratteri anziché su punti di codice, che è ciò che si desidera String sia composto, non solo su unità di caratteri. Peccato che Java non ti permetta di usare OO per risolvere questo problema, ma sia la Stringclasse che le StringBufferclassi sono state finalridimensionate. Dimmi, non è un eufemismo per i morti? :)
tchrist

2
@tchrist La documentazione (e la fonte) afferma che si inverte come una stringa di punti di codice. (Presumibilmente 1.0.2 non lo ha fatto, e in questi giorni non avresti mai avuto un tale cambiamento di comportamento.)
Tom Hawtin - affronta

Risposte:


127

Il termine "coppia surrogata" si riferisce a un mezzo per codificare i caratteri Unicode con punti di codice elevati nello schema di codifica UTF-16.

Nella codifica dei caratteri Unicode, i caratteri sono associati a valori compresi tra 0x0 e 0x10FFFF.

Internamente, Java utilizza lo schema di codifica UTF-16 per memorizzare stringhe di testo Unicode. In UTF-16, vengono utilizzate unità di codice a 16 bit (due byte). Poiché 16 bit possono contenere solo l'intervallo di caratteri compreso tra 0x0 e 0xFFFF, viene utilizzata una complessità aggiuntiva per memorizzare valori al di sopra di questo intervallo (da 0x10000 a 0x10FFFF). Questo viene fatto usando coppie di unità di codice note come surrogati.

Le unità di codice surrogato si trovano in due intervalli noti come "surrogati elevati" e "surrogati bassi", a seconda che siano consentiti all'inizio o alla fine della sequenza di unità di due codici.


4
questo ha il maggior numero di voti, ma non fornisce un singolo esempio di codice. Né una di queste risposte su come usarlo. Questo è il motivo per cui questo è stato sottoposto a downgrade.
George Xavier,

57

Le prime versioni di Java rappresentavano i caratteri Unicode usando il tipo di dati char a 16 bit. Questo design aveva senso all'epoca, perché tutti i caratteri Unicode avevano valori inferiori a 65.535 (0xFFFF) e potevano essere rappresentati in 16 bit. Successivamente, tuttavia, Unicode ha aumentato il valore massimo a 1.114.111 (0x10FFFF). Poiché i valori a 16 bit erano troppo piccoli per rappresentare tutti i caratteri Unicode in Unicode versione 3.1, i valori a 32 bit - chiamati punti di codice - sono stati adottati per lo schema di codifica UTF-32. Ma i valori a 16 bit sono preferiti ai valori a 32 bit per un uso efficiente della memoria, quindi Unicode ha introdotto un nuovo design per consentire l'uso continuo di valori a 16 bit. Questo design, adottato nello schema di codifica UTF-16, assegna 1.024 valori a surrogati alti a 16 bit (nell'intervallo U + D800 a U + DBFF) e altri 1.024 valori a surrogati bassi a 16 bit (nell'intervallo U + DC00 a U + DFFF).


7
Mi piace meglio della risposta accettata, poiché spiega come Unicode 3.1 abbia riservato 1024 + 1024 (alto + basso) valori rispetto al 65535 originale per ottenere 1024 * 1024 nuovi valori, senza requisiti aggiuntivi che i parser iniziano all'inizio di un corda.
Eric Hirst,

1
Non mi piace questa risposta per implicare che UTF-16 è la codifica Unicode più efficiente in termini di memoria. UTF-8 esiste e non esegue il rendering della maggior parte del testo come due byte. UTF-16 viene utilizzato principalmente oggi perché Microsoft l'ha scelto prima che UTF-32 fosse una cosa, non per l'efficienza della memoria. Circa l'unica volta che ci si effettivamente desidera UTF-16 è quando si sta facendo un sacco di gestione dei file su Windows, e sono quindi sia in lettura e scrittura di un sacco. Altrimenti, UTF-32 per alta velocità (offset costanti b / c) o UTF-8 per memoria insufficiente (b / c minimo 1 byte)
Fund Monica's Lawsuit

23

Ciò che la documentazione dice è che le stringhe UTF-16 non valide possono diventare valide dopo aver chiamato il reversemetodo poiché potrebbero essere il contrario di stringhe valide. Una coppia surrogata (discussa qui ) è una coppia di valori a 16 bit in UTF-16 che codificano un singolo punto di codice Unicode; i surrogati bassi e alti sono le due metà di quella codifica.


6
Una precisazione. Una stringa deve essere invertita su caratteri "veri" (alias "grafemi" o "elementi di testo"). Un singolo punto di codice "carattere" potrebbe essere uno o due blocchi "char" (coppia surrogata) e un grafema potrebbe essere uno o più di quei punti di codice (cioè un codice di carattere di base più uno o più codici di carattere combinati, ognuno dei quali potrebbe essere uno o due blocchi di 16 bit o "caratteri" lunghi). Quindi un singolo grafema potrebbe essere tre combinando personaggi ciascuno con due "caratteri" lunghi, per un totale di 6 "caratteri". Tutti e 6 i "caratteri" devono essere tenuti insieme, in ordine (cioè non invertiti), quando si inverte l'intera stringa di caratteri.
Triynko,

4
Quindi il tipo di dati "char" è piuttosto fuorviante. "personaggio" è un termine generico. Il tipo "char" è in realtà solo la dimensione del blocco UTF16 e lo chiamiamo carattere a causa della rarità relativa delle coppie surrogate che si verificano (cioè di solito rappresenta un punto di codice di un intero carattere), quindi "carattere" si riferisce in realtà a un singolo punto di codice unicode , ma poi con i caratteri combinati, puoi avere una sequenza di caratteri che vengono visualizzati come un singolo "elemento carattere / grapheme / testo". Questa non è scienza missilistica; i concetti sono semplici, ma il linguaggio è confuso.
Triynko,

Al momento dello sviluppo di Java, Unicode era agli inizi. Java era in circolazione da circa 5 anni prima che Unicode ottenesse coppie surrogate, quindi un carattere a 16 bit si adattava abbastanza bene al momento. Ora, stai molto meglio usando UTF-8 e UTF-32 che UTF-16.
Jonathan Baldwin,

23

Aggiunta di ulteriori informazioni alle risposte di cui sopra da questo post.

Testato in Java-12, dovrebbe funzionare in tutte le versioni Java sopra la 5.

Come menzionato qui: https://stackoverflow.com/a/47505451/2987755 ,
qualunque personaggio (il cui Unicode è sopra U + FFFF) è rappresentato come coppia surrogata, che Java memorizza come coppia di valori char, ovvero il singolo Unicode il personaggio è rappresentato da due caratteri Java adiacenti.
Come possiamo vedere nel seguente esempio.
1. Lunghezza:

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

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

2. Uguaglianza:
rappresentare "🌉" in String usando Unicode \ud83c\udf09come di seguito e verificare l'uguaglianza.

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

Java non supporta UTF-32

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

3. È possibile convertire il carattere Unicode in Java String

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

4. String.substring () non considera i caratteri supplementari

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

Per risolvere questo possiamo usare String.offsetByCodePoints(int index, int codePointOffset)

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

5. stringa Iterando Unicode con BreakIterator
6. ordinamento delle stringhe con Unicode java.text.Collator
7. del personaggio toUpperCase(), toLowerCase()metodi non devono essere utilizzati, invece, maiuscolo uso String e minuscole di localizzazione particolare.
8. Character.isLetter(char ch)non supporta, meglio usato Character.isLetter(int codePoint), per ogni methodName(char ch)metodo nella classe Character ci sarà un tipo di methodName(int codePoint)che può gestire caratteri supplementari.
9. Specificare charset in String.getBytes(), la conversione da byte a stringa, InputStreamReader,OutputStreamWriter

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

Ulteriori informazioni sull'esempio image1 image2
Altri termini che vale la pena esplorare: normalizzazione , BiDi


2
ho effettuato l'accesso appositamente per votare questa risposta (intendo cambiare la finestra da in incognito a normale: P). Migliore spiegazione per un noob
N-JOY,

1
Grazie !, Sono contento che abbia aiutato, ma l'autore originale del post merita tutto l'apprezzamento.
dkb

Grandi esempi! Ho effettuato il login anche per votarlo :) E ancora, mi ha fatto pensare (di nuovo) che davvero non capisco perché Java mantenga vivi i bug nel loro codice. Rispetto totalmente che non vogliono violare il codice esistente, ma dai ... quante ore sono andate perse a ovviare a questi bug? Se è rotto, riparalo, dannazione!
Franz D.


6

Prefazione piccola

  • Unicode rappresenta punti di codice. Ogni punto di codice può essere codificato in blocchi di 8, 16, - o 32 bit secondo lo standard Unicode.
  • Prima della versione 3.1, la maggior parte in uso era la codifica a 8 bit, nota come UTF-8, e la codifica a 16 bit, nota come UCS-2 o "Set di caratteri universale codificato in 2 ottetti". UTF-8 codifica i punti Unicode come una sequenza di blocchi da 1 byte, mentre UCS-2 richiede sempre 2 byte:

    A = 41 - un blocco di 8 bit con UTF-8
    A = 0041 - un blocco di 16 bit con UCS-2
    Ω = CE A9 - due blocchi di 8 bit con UTF-8
    Ω = 03A9 - un blocco di 16 bit con UCS-2

Problema

Il consorzio ha pensato che 16 bit sarebbero stati sufficienti a coprire qualsiasi linguaggio leggibile dall'uomo, il che fornisce 2 ^ 16 = 65536 possibili valori di codice. Questo era vero per il piano 0, noto anche come BPM o piano multilingue di base, che oggi comprende 55.445 di 65536 punti di codice. BPM copre quasi ogni lingua umana nel mondo, compresi i simboli cinese-giapponese-coreano (CJK).

Il tempo è passato e sono stati aggiunti nuovi set di caratteri asiatici, i simboli cinesi hanno preso più di 70.000 punti da soli. Ora, ci sono anche punti Emoji come parte dello standard 😺. Sono stati aggiunti 16 nuovi aerei "aggiuntivi" . La stanza UCS-2 non era abbastanza per coprire qualcosa di più grande del piano-0.

Decisione Unicode

  1. Limita Unicode ai 17 piani × 65 536 caratteri per piano = 1 114 112 punti massimi.
  2. Presente UTF-32, precedentemente noto come UCS-4, per contenere 32 bit per ciascun punto di codice e coprire tutti i piani.
  3. Continuare a utilizzare UTF-8 come codifica dinamica, limitare UTF-8 a un massimo di 4 byte per ciascun punto di codice, ovvero da 1 a 4 byte per punto.
  4. Deprecare UCS-2
  5. Crea UTF-16 basato su UCS-2. Rendi dinamico UTF-16, quindi sono necessari 2 byte o 4 byte per punto. Assegna 1024 punti U + D800 – U + DBFF, chiamati High Surrogates, a UTF-16; assegnare 1024 simboli U + DC00 – U + DFFF, chiamati Low Surrogates, a UTF-16.

    Con queste modifiche, il BPM è coperto con 1 blocco di 16 bit in UTF-16, mentre tutti i "caratteri supplementari" sono coperti con coppie surrogate che presentano 2 blocchi di 16 bit ciascuno, in totale 1024x1024 = 1 048 576 punti.

    Un surrogato alto precede un surrogato basso . Qualsiasi deviazione da questa regola è considerata una codifica errata. Ad esempio, un surrogato senza coppia non è corretto, un surrogato basso in piedi prima di un surrogato alto non è corretto.

    𝄞, 'SIMBOLO MUSICALE G CLEF', è codificato in UTF-16 come coppia di surrogati 0xD834 0xDD1E (2 per 2 byte),
    in UTF-8 come 0xF0 0x9D 0x84 0x9E (4 per 1 byte),
    in UTF-32 come 0x0001D11E (1 per 4 byte).

Situazione attuale

  • Sebbene in base allo standard i surrogati siano assegnati specificamente solo a UTF-16, storicamente alcune applicazioni Windows e Java utilizzavano punti UTF-8 e UCS-2 riservati ora alla gamma di surrogati.
    Per supportare le applicazioni legacy con codifiche UTF-8 / UTF-16 errate , è stato creato un nuovo standard WTF-8 , Wobbly Transformation Format. Supporta punti surrogati arbitrari, come un surrogato non associato o una sequenza errata. Oggi alcuni prodotti non sono conformi allo standard e trattano UTF-8 come WTF-8.
  • La soluzione surrogata ha aperto molti problemi di sicurezza nella conversione tra diverse codifiche, la maggior parte delle quali è stata gestita bene.

Molti dettagli storici sono stati soppressi per seguire l'argomento ⚖.
L'ultimo Unicode Standard è disponibile all'indirizzo http://www.unicode.org/versions/latest


3

Una coppia surrogata è due 'unità di codice' in UTF-16 che compongono un 'punto di codice'. La documentazione Java afferma che questi "punti di codice" saranno ancora validi, con le loro "unità di codice" ordinate correttamente, dopo il contrario. Dichiara inoltre che due unità di codice surrogato non accoppiate possono essere invertite e formare una coppia surrogata valida. Ciò significa che se ci sono unità di codice non accoppiate, allora c'è la possibilità che il contrario del contrario potrebbe non essere lo stesso!

Si noti, tuttavia, che la documentazione non dice nulla su Graphemes, che sono più punti di codice combinati. Il che significa che e l'accento che ne consegue può ancora essere commutato, ponendo così l'accento prima della e. Ciò significa che se c'è un'altra vocale prima della e, potrebbe ottenere l'accento che era sulla e.

Yikes!

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.