2010-04-28 12 views
11

Qual è il modo più efficiente per calcolare la lunghezza in byte di un carattere, tenendo conto della codifica del carattere? La codifica sarebbe nota solo durante il runtime. Ad esempio, in UTF-8 i caratteri hanno una lunghezza di byte variabile, quindi ogni carattere deve essere determinato individualmente. Per quanto ora mi è venuta in mente questo:Modo efficiente per calcolare la lunghezza in byte di un carattere, a seconda della codifica

char c = getCharSomehow(); 
String encoding = getEncodingSomehow(); 
// ... 
int length = new String(new char[] { c }).getBytes(encoding).length; 

Ma questo è goffo e inefficace in un ciclo dal momento che un new String bisogno di creare ogni volta. Non riesco a trovare altri modi più efficienti nell'API Java. C'è un String#valueOf(char), ma in base alla sua fonte fa sostanzialmente lo stesso di sopra. Immagino che questo può essere fatto con le operazioni bit per bit come bit di spostamento, ma questo è il mio punto debole e io sono sicuro come prendere la codifica in considerazione qui :)

Se si mette in discussione la necessità di questo, controllare this topic .


Update: la risposta da @Bkkbrad è tecnicamente il più efficiente:

char c = getCharSomehow(); 
String encoding = getEncodingSomehow(); 
CharsetEncoder encoder = Charset.forName(encoding).newEncoder(); 
// ... 
int length = encoder.encode(CharBuffer.wrap(new char[] { c })).limit(); 

Tuttavia, come @Stephen C sottolineato, ci sono più problemi con questo. Ad esempio, potrebbero esserci caratteri combinati/surrogati che devono essere presi in considerazione. Ma questo è un altro problema che deve essere risolto nel passaggio prima di questo passaggio.

+0

Utilizzando quanto sopra hai avuto problemi di prestazioni? Vuoi sempre usare UTF-8? –

+0

L'esempio era effettivamente un po 'fuorviante, ma in realtà la codifica può essere determinata solo durante il runtime. Ho aggiornato la domanda. Dopo tutto, questo non sembra essere un compito facile però. – BalusC

+3

questo è completamente sbagliato e quindi è la risposta di bkkbrad. In realtà è abbastanza spaventoso vedere così tante persone completamente sbagliate su quello (+1 solo alla risposta di bkail). Un Java * char * fa ** non **, ripeto ** A JAVA CHAR NON ** rappresenta più un personaggio da Java 1.4/Unicode 3.1. * String.value (char) * e wrapping "char" * e whatnots sono tutti metodi degli anni '90. Il mondo è andato avanti ed è passato molto tempo che Unicode ha più di 65 536 codepoint. Usa "int", prendi "char" fuori di testa perché Java char è rotto irreparabilmente. \t ♩ \t ♩ \t ♩ \t ♩ – SyntaxT3rr0r

risposta

10

Utilizzare un CharsetEncoder e riutilizzare un CharBuffer come input e un ByteBuffer come output.

Sul mio sistema, il seguente codice richiede 25 secondi per codificare 100.000 singoli caratteri:

Charset utf8 = Charset.forName("UTF-8"); 
char[] array = new char[1]; 
for (int reps = 0; reps < 10000; reps++) { 
    for (array[0] = 0; array[0] < 10000; array[0]++) { 
     int len = new String(array).getBytes(utf8).length; 
    } 
} 

Tuttavia, il seguente codice fa la stessa cosa in meno di 4 secondi:

Charset utf8 = Charset.forName("UTF-8"); 
CharsetEncoder encoder = utf8.newEncoder(); 
char[] array = new char[1]; 
CharBuffer input = CharBuffer.wrap(array); 
ByteBuffer output = ByteBuffer.allocate(10); 
for (int reps = 0; reps < 10000; reps++) { 
    for (array[0] = 0; array[0] < 10000; array[0]++) { 
     output.clear(); 
     input.clear(); 
     encoder.encode(input, output, false); 
     int len = output.position(); 
    } 
} 

Edit : Perché gli odiatori devono odiare?

Ecco una soluzione che legge da un CharBuffer e tiene traccia di surrogate pairs:

Charset utf8 = Charset.forName("UTF-8"); 
CharsetEncoder encoder = utf8.newEncoder(); 
CharBuffer input = //allocate in some way, or pass as parameter 
ByteBuffer output = ByteBuffer.allocate(10); 

int limit = input.limit(); 
while(input.position() < limit) { 
    output.clear(); 
    input.mark(); 
    input.limit(Math.max(input.position() + 2, input.capacity())); 
    if (Character.isHighSurrogate(input.get()) && !Character.isLowSurrogate(input.get())) { 
     //Malformed surrogate pair; do something! 
    } 
    input.limit(input.position()); 
    input.reset(); 
    encoder.encode(input, output, false); 
    int encodedLen = output.position(); 
} 
+1

Tecnicamente, questa è la migliore risposta (se si sostituisce 'position()' di 'limit()'). Questo è davvero molto più efficiente. – BalusC

+1

@Bkkbrad: un carattere Java è totalmente inadeguato dal 1993 o giù di lì per rappresentare un carattere Unicode, quando Unicode si spostava a 1.1 e aveva più di 65 536 codepoint. Il metodo da utilizzare per ottenere un carattere in Java è String * codePointAt (..) * che restituisce correttamente * int *. Java * char * è, beh, completamente rotto. (200 KLOC codebase qui e stiamo usando Java char, beh ... ** zero ** volte). – SyntaxT3rr0r

+1

@WizardOfOdds: ho aggiunto una soluzione per tenere traccia delle coppie di surrogati. – Bkkbrad

3

È possibile che uno schema di codifica possa codificare un dato carattere come un numero variabile di byte, a seconda di ciò che viene prima e dopo di esso nella sequenza di caratteri. La lunghezza in byte ottenuta dalla codifica di un singolo carattere Stringa non è quindi l'intera risposta.

(Ad esempio, potresti teoricamente ricevere caratteri baudot/teletype codificati come 4 caratteri ogni 3 byte, oppure potresti teoricamente trattare un UTF-16 + un compressore di flusso come uno schema di codifica.) Sì, è tutto un po ' non plausibile, ma ...)

+0

Sì, il punto è che i caratteri sostitutivi devono essere presi in considerazione prima o poi. – BalusC

3

Se è possibile garantire che l'input sia ben formato UTF-8, non c'è motivo di trovare punti di codice. Uno dei punti di forza di UTF-8 è che è possibile rilevare l'inizio di un punto di codice da qualsiasi posizione nella stringa. Cerca semplicemente all'indietro finché non trovi un byte tale (b & 0xc0)! = 0x80, e hai trovato un altro carattere. Poiché un punto codice codificato UTF-8 è sempre 6 byte o meno, è possibile copiare i byte intermedi in un buffer a lunghezza fissa.

Edit: Ho dimenticato di dire, anche se non si va con questa strategia, non è sufficiente utilizzare un "char" Java per memorizzare i punti di codice arbitrario in quanto i valori del punto di codice possono superare 0xffff. È necessario memorizzare i punti di codice in un "int".

+0

Un ottimo consiglio. Sfortunatamente non c'è garanzia al 100%. – BalusC

+0

@bkail: +1 a te perché sei l'unico in questo thread a menzionare che un * char * di Java non può archiviare codepoint arbitrari e che invece * dovrebbe essere usato * int *. – SyntaxT3rr0r

1

Prova Charset.forName("UTF-8").encode("string").limit(); Potrebbe essere un po 'più efficiente, forse no.

+0

Questo richiede ancora un 'String' come input. – BalusC

Problemi correlati