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.