Lunghezza della stringa in byte in JavaScript


104

Nel mio codice JavaScript ho bisogno di comporre un messaggio al server in questo formato:

<size in bytes>CRLF
<data>CRLF

Esempio:

3
foo

I dati possono contenere caratteri Unicode. Devo inviarli come UTF-8.

Sto cercando il modo più cross-browser per calcolare la lunghezza della stringa in byte in JavaScript.

Ho provato questo per comporre il mio carico utile:

return unescape(encodeURIComponent(str)).length + "\n" + str + "\n"

Ma non mi dà risultati accurati per i browser più vecchi (o forse le stringhe in quei browser in UTF-16?).

Qualche indizio?

Aggiornare:

Esempio: la lunghezza in byte della stringa ЭЭХ! Naïve?in UTF-8 è di 15 byte, ma alcuni browser riportano invece 23 byte.



@Eli: nessuna delle risposte nella domanda che hai collegato a lavorare per me.
Alexander Gladysh

Quando parli di "ЭЭХ! Naïve?" l'hai messo in una particolare forma normale? unicode.org/reports/tr15
Mike Samuel

@ Mike: l'ho digitato nell'editor di testo casuale (in modalità UTF-8) e l'ho salvato. Proprio come farebbe qualsiasi utente della mia libreria. Tuttavia, sembra che ho capito cosa c'era che non andava - vedi la mia risposta.
Alexander Gladysh

Risposte:


89

Non c'è modo di farlo in JavaScript in modo nativo. (Vedi la risposta di Riccardo Galli per un approccio moderno.)


Per riferimento storico o dove le API di TextEncoder non sono ancora disponibili .

Se conosci la codifica dei caratteri, puoi calcolarla da solo.

encodeURIComponent assume UTF-8 come codifica dei caratteri, quindi se hai bisogno di quella codifica, puoi farlo,

function lengthInUtf8Bytes(str) {
  // Matches only the 10.. bytes that are non-initial characters in a multi-byte sequence.
  var m = encodeURIComponent(str).match(/%[89ABab]/g);
  return str.length + (m ? m.length : 0);
}

Questo dovrebbe funzionare a causa del modo in cui UTF-8 codifica le sequenze multibyte. Il primo byte codificato inizia sempre con un bit alto pari a zero per una singola sequenza di byte o un byte la cui prima cifra esadecimale è C, D, E o F. Il secondo byte e i successivi sono quelli i cui primi due bit sono 10 Questi sono i byte extra che vuoi contare in UTF-8.

La tabella in wikipedia lo rende più chiaro

Bits        Last code point Byte 1          Byte 2          Byte 3
  7         U+007F          0xxxxxxx
 11         U+07FF          110xxxxx        10xxxxxx
 16         U+FFFF          1110xxxx        10xxxxxx        10xxxxxx
...

Se invece hai bisogno di capire la codifica della pagina, puoi usare questo trucco:

function lengthInPageEncoding(s) {
  var a = document.createElement('A');
  a.href = '#' + s;
  var sEncoded = a.href;
  sEncoded = sEncoded.substring(sEncoded.indexOf('#') + 1);
  var m = sEncoded.match(/%[0-9a-f]{2}/g);
  return sEncoded.length - (m ? m.length * 2 : 0);
}

Ebbene, come faccio a conoscere la codifica dei caratteri dei dati? Ho bisogno di codificare qualsiasi stringa utente (programmatore) fornita alla mia libreria JS.
Alexander Gladysh

@Alexander, quando invii il messaggio al server, stai specificando la codifica del contenuto del corpo del messaggio tramite un'intestazione HTTP?
Mike Samuel

1
@Alexander, fantastico. Se stai stabilendo un protocollo, imporre UTF-8 è un'ottima idea per lo scambio di testo. Una variabile in meno che può causare una mancata corrispondenza. UTF-8 dovrebbe essere l'ordine dei byte di rete delle codifiche dei caratteri.
Mike Samuel

4
@ MikeSamuel: La lengthInUtf8Bytesfunzione restituisce 5 per i caratteri non BMP come str.lengthper questi restituisce 2. Scriverò una versione modificata di questa funzione nella sezione delle risposte.
Lauri Oherd

1
Questa soluzione è interessante ma utf8mb4 non è considerata. Ad esempio, encodeURIComponent('🍀')è '%F0%9F%8D%80'.
albert

117

Gli anni sono passati e oggi puoi farlo in modo nativo

(new TextEncoder().encode('foo')).length

Nota che non è ancora supportato da IE (o Edge) (puoi usare un polyfill per quello).

Documentazione MDN

Specifiche standard


4
Che approccio fantastico e moderno. Grazie!
Con Antonakos

Si noti che secondo la documentazione di MDN , TextEncoder non è ancora supportato da Safari (WebKit).
Maor

TextEncodesupporta solo utf-8 a partire da Chrome 53.
Jehong Ahn

1
Se è necessaria solo la lunghezza, potrebbe essere eccessivo allocare una nuova stringa, eseguire la conversione effettiva, prendere la lunghezza e quindi eliminare la stringa. Vedi la mia risposta sopra per una funzione che calcola la lunghezza in modo efficiente.
lovasoa

66

Ecco una versione molto più veloce, che non utilizza espressioni regolari, né encodeURIComponent () :

function byteLength(str) {
  // returns the byte length of an utf8 string
  var s = str.length;
  for (var i=str.length-1; i>=0; i--) {
    var code = str.charCodeAt(i);
    if (code > 0x7f && code <= 0x7ff) s++;
    else if (code > 0x7ff && code <= 0xffff) s+=2;
    if (code >= 0xDC00 && code <= 0xDFFF) i--; //trail surrogate
  }
  return s;
}

Ecco un confronto delle prestazioni .

Calcola semplicemente la lunghezza in UTF8 di ogni codepoint unicode restituito da charCodeAt () (in base alle descrizioni di wikipedia dei caratteri surrogati UTF8 e UTF16).

Segue RFC3629 (dove i caratteri UTF-8 sono lunghi al massimo 4 byte).


46

Per una semplice codifica UTF-8, con una compatibilità leggermente migliore di TextEncoder, Blob fa il trucco. Tuttavia, non funziona con browser molto vecchi.

new Blob(["😀"]).size; // -> 4  

29

Questa funzione restituirà la dimensione in byte di qualsiasi stringa UTF-8 che le passi.

function byteCount(s) {
    return encodeURI(s).split(/%..|./).length - 1;
}

fonte


non funziona con la stringa 'ユ ー ザ ー コ ー ド', lunghezza prevista 14 ma 21
May Weather VN

1
@ MayWeatherVN la ユーザーコードlunghezza sbagliata in byte è sempre 21, l'ho testata su strumenti diversi; sii più gentile con i tuoi commenti;)
Capitex

Questa stringa che ricordo di aver testato su php è 14
May Weather VN

23

Un altro approccio molto semplice utilizzando Buffer(solo per NodeJS):

Buffer.byteLength(string, 'utf8')

Buffer.from(string).length

1
Puoi saltare la creazione di un buffer con Buffer.byteLength(string, 'utf8').
Joe,

1
@ Joe Grazie per il suggerimento, ho appena apportato una modifica per includerlo.
Iván Pérez il

5

Mi ci è voluto un po 'per trovare una soluzione per React Native, quindi la metterò qui:

Prima installa il bufferpacchetto:

npm install --save buffer

Quindi utilizza il metodo del nodo:

const { Buffer } = require('buffer');
const length = Buffer.byteLength(string, 'utf-8');

4

In realtà, ho capito cosa c'è che non va. Affinché il codice funzioni, la pagina <head>dovrebbe avere questo tag:

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

Oppure, come suggerito nei commenti, se il server invia Content-Encodingun'intestazione HTTP , dovrebbe funzionare anche lei.

Quindi i risultati di browser diversi sono coerenti.

Ecco un esempio:

<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
  <title>mini string length test</title>
</head>
<body>

<script type="text/javascript">
document.write('<div style="font-size:100px">' 
    + (unescape(encodeURIComponent("ЭЭХ! Naïve?")).length) + '</div>'
  );
</script>
</body>
</html>

Nota: sospetto che la specifica di una codifica (accurata) risolverebbe il problema della codifica. È solo una coincidenza che ho bisogno di UTF-8.


2
La unescapefunzione JavaScript non deve essere utilizzata per decodificare URI (Uniform Resource Identifiers).
Lauri Oherd

1
@LauriOherd non unescapedovrebbe infatti mai essere usato per decodificare gli URI. Tuttavia, per convertire il testo in UTF-8 funziona bene
TS

unescape(encodeURIComponent(...)).lengthcalcola sempre la lunghezza corretta con o senza meta http-equiv ... utf8. Senza una specifica di codifica, alcuni browser potrebbero semplicemente avere un testo diverso (dopo aver codificato i byte del documento in testo html effettivo) di cui hanno calcolato la lunghezza. Si potrebbe testarlo facilmente, stampando non solo la lunghezza, ma anche il testo stesso.
TS

3

Ecco un metodo indipendente ed efficiente per contare i byte UTF-8 di una stringa.

//count UTF-8 bytes of a string
function byteLengthOf(s){
	//assuming the String is UCS-2(aka UTF-16) encoded
	var n=0;
	for(var i=0,l=s.length; i<l; i++){
		var hi=s.charCodeAt(i);
		if(hi<0x0080){ //[0x0000, 0x007F]
			n+=1;
		}else if(hi<0x0800){ //[0x0080, 0x07FF]
			n+=2;
		}else if(hi<0xD800){ //[0x0800, 0xD7FF]
			n+=3;
		}else if(hi<0xDC00){ //[0xD800, 0xDBFF]
			var lo=s.charCodeAt(++i);
			if(i<l&&lo>=0xDC00&&lo<=0xDFFF){ //followed by [0xDC00, 0xDFFF]
				n+=4;
			}else{
				throw new Error("UCS-2 String malformed");
			}
		}else if(hi<0xE000){ //[0xDC00, 0xDFFF]
			throw new Error("UCS-2 String malformed");
		}else{ //[0xE000, 0xFFFF]
			n+=3;
		}
	}
	return n;
}

var s="\u0000\u007F\u07FF\uD7FF\uDBFF\uDFFF\uFFFF";
console.log("expect byteLengthOf(s) to be 14, actually it is %s.",byteLengthOf(s));

Si noti che il metodo può generare un errore se una stringa di input è UCS-2 non valida


3

In NodeJS, Buffer.byteLengthè un metodo specifico per questo scopo:

let strLengthInBytes = Buffer.byteLength(str); // str is UTF-8

Si noti che per impostazione predefinita il metodo presuppone che la stringa sia nella codifica UTF-8. Se è richiesta una codifica diversa, passala come secondo argomento.


È possibile calcolare strLengthInBytessolo conoscendo il "conteggio" dei caratteri all'interno della stringa? cioè var text = "Hello World!; var text_length = text.length; // pass text_length as argument to some method?. E, proprio per riferimento, ri Buffer- mi sono imbattuto in questa risposta che discute new Blob(['test string']).sizee, in nodo Buffer.from('test string').length. Forse questi aiuteranno anche alcune persone?
user1063287

1
@ user1063287 Il problema è che il numero di caratteri non è sempre equivalente al numero di byte. Ad esempio, la comune codifica UTF-8 è una codifica a larghezza variabile, in cui un singolo carattere può avere una dimensione compresa tra 1 byte e 4 byte. Ecco perché è necessario un metodo speciale oltre alla codifica utilizzata.
Boaz

Ad esempio, una stringa UTF-8 con 4 caratteri, può essere lunga almeno 4 byte, se ogni carattere è solo 1 byte; e al massimo 16 byte "lunghi" se ogni carattere è di 4 byte. Nota in entrambi i casi il conteggio dei caratteri è ancora 4 ed è quindi una misura inaffidabile per la lunghezza dei byte .
Boaz,

1

Funzionerebbe per i caratteri BMP e SIP / SMP.

    String.prototype.lengthInUtf8 = function() {
        var asciiLength = this.match(/[\u0000-\u007f]/g) ? this.match(/[\u0000-\u007f]/g).length : 0;
        var multiByteLength = encodeURI(this.replace(/[\u0000-\u007f]/g)).match(/%/g) ? encodeURI(this.replace(/[\u0000-\u007f]/g, '')).match(/%/g).length : 0;
        return asciiLength + multiByteLength;
    }

    'test'.lengthInUtf8();
    // returns 4
    '\u{2f894}'.lengthInUtf8();
    // returns 4
    'سلام علیکم'.lengthInUtf8();
    // returns 19, each Arabic/Persian alphabet character takes 2 bytes. 
    '你好,JavaScript 世界'.lengthInUtf8();
    // returns 26, each Chinese character/punctuation takes 3 bytes. 

0

Puoi provare questo:

function getLengthInBytes(str) {
  var b = str.match(/[^\x00-\xff]/g);
  return (str.length + (!b ? 0: b.length)); 
}

Per me funziona.


restituisce 1 per "â" in chrome
Rick

il primo problema potrebbe essere risolto cambiando \ xff in \ x7f, ma ciò non risolve il fatto che i codepoint tra 0x800-0xFFFF verranno segnalati come occupanti 2 byte, quando ne prendono 3.
Rick
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.