Zeichenfolgenlänge in Bytes in JavaScript

104

In meinem JavaScript-Code muss ich eine Nachricht an den Server in diesem Format verfassen:

<size in bytes>CRLF
<data>CRLF

Beispiel:

3
foo

Die Daten können Unicode-Zeichen enthalten. Ich muss sie als UTF-8 senden.

Ich suche nach der browserübergreifendsten Methode, um die Länge der Zeichenfolge in Byte in JavaScript zu berechnen.

Ich habe dies versucht, um meine Nutzlast zusammenzustellen:

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

Aber es gibt mir keine genauen Ergebnisse für die älteren Browser (oder vielleicht die Zeichenfolgen in diesen Browsern in UTF-16?).

Irgendwelche Hinweise?

Aktualisieren:

Beispiel: Die Länge der Zeichenfolge ЭЭХ! Naïve?in UTF-8 in Byte beträgt 15 Byte. Einige Browser melden jedoch stattdessen 23 Byte.

Alexander Gladysh
quelle
1
Mögliches Duplikat? stackoverflow.com/questions/2219526/…
Eli
@Eli: Keine der Antworten in der Frage, die Sie verlinkt haben, funktioniert für mich.
Alexander Gladysh
Wenn Sie über "ЭЭХ! Naiv?" Hast du es in eine bestimmte normale Form gebracht? unicode.org/reports/tr15
Mike Samuel
@ Mike: Ich habe es im zufälligen Texteditor (im UTF-8-Modus) eingegeben und gespeichert. Genau wie jeder Benutzer meiner Bibliothek. Es scheint jedoch, dass ich herausgefunden habe, was falsch war - siehe meine Antwort.
Alexander Gladysh

Antworten:

89

Es gibt keine Möglichkeit, dies nativ in JavaScript zu tun. (Siehe Riccardo Gallis Antwort für einen modernen Ansatz.)


Zur historischen Bezugnahme oder wenn TextEncoder-APIs noch nicht verfügbar sind .

Wenn Sie die Zeichenkodierung kennen, können Sie sie jedoch selbst berechnen.

encodeURIComponent nimmt UTF-8 als Zeichencodierung an. Wenn Sie diese Codierung benötigen, können Sie Folgendes tun:

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);
}

Dies sollte aufgrund der Art und Weise funktionieren, wie UTF-8 Mehrbyte-Sequenzen codiert. Das erste codierte Byte beginnt immer entweder mit einem hohen Bit von Null für eine einzelne Bytesequenz oder mit einem Byte, dessen erste hexadezimale Ziffer C, D, E oder F ist. Das zweite und nachfolgende Byte sind diejenigen, deren erste zwei Bits 10 sind Dies sind die zusätzlichen Bytes, die Sie in UTF-8 zählen möchten.

Die Tabelle in Wikipedia macht es klarer

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
...

Wenn Sie stattdessen die Seitencodierung verstehen müssen, können Sie diesen Trick verwenden:

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);
}
Mike Samuel
quelle
Wie würde ich die Zeichenkodierung der Daten kennen? Ich muss den String-Benutzer (Programmierer) codieren, der meiner JS-Bibliothek zur Verfügung gestellt wird.
Alexander Gladysh
@Alexander Geben Sie beim Senden der Nachricht an den Server die Inhaltscodierung des Nachrichtentexts über einen HTTP-Header an?
Mike Samuel
1
@ Alexander, cool. Wenn Sie ein Protokoll erstellen, ist das Mandatieren von UTF-8 eine gute Idee für den Textaustausch. Eine Variable weniger, die zu einer Nichtübereinstimmung führen kann. UTF-8 sollte die Netzwerkbyte-Reihenfolge der Zeichencodierungen sein.
Mike Samuel
4
@MikeSamuel: Die lengthInUtf8BytesFunktion gibt 5 für Nicht-BMP-Zeichen zurück, wie str.lengthfür diese Rückgaben 2. Ich werde eine modifizierte Version dieser Funktion in den Antwortabschnitt schreiben.
Lauri Oherd
1
Diese Lösung ist cool, aber utf8mb4 wird nicht berücksichtigt. Zum Beispiel encodeURIComponent('🍀')ist '%F0%9F%8D%80'.
Albert
117

Jahre vergingen und heutzutage kann man es nativ machen

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

Beachten Sie, dass es vom IE (oder Edge) noch nicht unterstützt wird (Sie können dafür eine Polyfüllung verwenden ).

MDN-Dokumentation

Standardspezifikationen

Riccardo Galli
quelle
4
Was für ein fantastischer, moderner Ansatz. Vielen Dank!
Con Antonakos
Beachten Sie, dass der TextEncoder gemäß der MDN-Dokumentation noch nicht von Safari (WebKit) unterstützt wird.
Maor
TextEncodeunterstützt nur utf-8 seit Chrome 53.
Jehong Ahn
1
Wenn Sie nur die Länge benötigen, ist es möglicherweise übertrieben, eine neue Zeichenfolge zuzuweisen, die eigentliche Konvertierung durchzuführen, die Länge zu übernehmen und die Zeichenfolge dann zu verwerfen. Siehe meine Antwort oben für eine Funktion, die nur die Länge auf effiziente Weise berechnet.
Lovasoa
66

Hier ist eine viel schnellere Version, die weder reguläre Ausdrücke noch encodeURIComponent () verwendet :

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;
}

Hier ist ein Leistungsvergleich .

Es berechnet lediglich die Länge aller von charCodeAt () zurückgegebenen Unicode-Codepunkte in UTF8 (basierend auf den Wikipedia-Beschreibungen von UTF8 und UTF16-Ersatzzeichen ).

Es folgt RFC3629 (wobei UTF-8-Zeichen höchstens 4 Byte lang sind).

Lovasoa
quelle
46

Für eine einfache UTF-8-Codierung mit etwas besserer Kompatibilität als TextEncoderBlob ist dies der Trick. Funktioniert jedoch nicht in sehr alten Browsern.

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

Diese Funktion gibt die Bytegröße aller UTF-8-Zeichenfolgen zurück, die Sie an sie übergeben.

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

Quelle

Lauri Oherd
quelle
es funktioniert nicht mit der Zeichenfolge 'ユ ー ザ ー コ ー ド', erwartete Länge 14, aber 21
Mai Wetter VN
1
@MayWeatherVN Sie falsche ユーザーコードLänge in Bytes ist immer 21, ich habe es auf verschiedenen Tools getestet; Sei freundlicher mit deinen Kommentaren;)
Capitex
Dieser String ich auf PHP erinnere mich testen, 14
Mai Wetter VN
24

Ein weiterer sehr einfacher Ansatz Buffer(nur für NodeJS):

Buffer.byteLength(string, 'utf8')

Buffer.from(string).length
Iván Pérez
quelle
1
Sie können das Erstellen eines Puffers mit überspringen Buffer.byteLength(string, 'utf8').
Joe
1
@ Joe Danke für den Vorschlag, ich habe gerade eine Bearbeitung vorgenommen, um ihn aufzunehmen.
Iván Pérez
5

Ich habe eine Weile gebraucht , um eine Lösung für React Native zu finden, also werde ich sie hier einfügen :

Installieren Sie zuerst das bufferPaket:

npm install --save buffer

Verwenden Sie dann die Knotenmethode:

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

Eigentlich habe ich herausgefunden, was los ist. Damit der Code funktioniert, sollte die Seite <head>dieses Tag haben:

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

Wenn der Server einen HTTP- Content-EncodingHeader sendet , sollte dies auch funktionieren , wie in den Kommentaren vorgeschlagen .

Dann sind die Ergebnisse von verschiedenen Browsern konsistent.

Hier ist ein Beispiel:

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

Hinweis: Ich vermute, dass die Angabe einer (genauen) Codierung das Codierungsproblem beheben würde. Es ist nur ein Zufall, dass ich UTF-8 brauche.

Alexander Gladysh
quelle
2
Die unescapeJavaScript-Funktion sollte nicht zum Dekodieren von URI (Uniform Resource Identifiers) verwendet werden.
Lauri Oherd
1
@LauriOherd unescapesollte in der Tat niemals zum Dekodieren von URIs verwendet werden. Um Text in UTF-8 zu konvertieren, funktioniert es jedoch einwandfrei
TS
unescape(encodeURIComponent(...)).lengthberechnet immer die richtige Länge mit oder ohne meta http-equiv ... utf8. Ohne eine Codierungsspezifikation könnten einige Browser einfach einen anderen Text haben (nachdem die Bytes des Dokuments in tatsächlichen HTML-Text codiert wurden), dessen Länge sie berechnet haben. Man könnte dies leicht testen, indem man nicht nur die Länge, sondern auch den Text selbst druckt.
TS
3

Hier ist eine unabhängige und effiziente Methode zum Zählen von UTF-8-Bytes einer Zeichenfolge.

//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));

Beachten Sie, dass die Methode möglicherweise einen Fehler auslöst, wenn eine Eingabezeichenfolge fehlerhaft ist

Fuweichin
quelle
3

In NodeJS Buffer.byteLengthist eine Methode speziell für diesen Zweck:

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

Beachten Sie, dass die Methode standardmäßig davon ausgeht, dass die Zeichenfolge in UTF-8-Codierung vorliegt. Wenn eine andere Codierung erforderlich ist, übergeben Sie diese als zweites Argument.

Boas
quelle
Ist es möglich, strLengthInBytesnur zu berechnen, indem man die Anzahl der Zeichen in der Zeichenfolge kennt? dh var text = "Hello World!; var text_length = text.length; // pass text_length as argument to some method?. Und nur als Referenz, Bufferich bin gerade auf diese Antwort gestoßen , die diskutiert new Blob(['test string']).sizeund im Knoten , Buffer.from('test string').length. Vielleicht helfen diese auch einigen Menschen?
user1063287
1
@ user1063287 Das Problem ist, dass die Anzahl der Zeichen nicht immer der Anzahl der Bytes entspricht. Beispielsweise ist die übliche UTF-8-Codierung eine Codierung mit variabler Breite, bei der ein einzelnes Zeichen eine Größe von 1 Byte bis 4 Byte haben kann. Aus diesem Grund wird neben der verwendeten Codierung eine spezielle Methode benötigt.
Boaz
Beispielsweise kann eine UTF-8-Zeichenfolge mit 4 Zeichen mindestens 4 Byte "lang" sein, wenn jedes Zeichen nur 1 Byte lang ist. und höchstens 16 Bytes "lang", wenn jedes Zeichen 4 Bytes groß ist. Beachten Sie, dass in beiden Fällen die Anzahl der Zeichen immer noch 4 beträgt und daher ein unzuverlässiges Maß für die Bytelänge ist .
Boaz
1

Dies würde für BMP- und SIP / SMP-Zeichen funktionieren.

    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. 
chrislau
quelle
0

Sie können dies versuchen:

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

Für mich geht das.

anh tran
quelle
gibt 1 für "â" in Chrom zurück
Rick
Das erste Problem könnte behoben werden, indem \ xff in \ x7f geändert wird. Dies behebt jedoch nicht die Tatsache, dass Codepunkte zwischen 0x800-0xFFFF als 2 Bytes lang gemeldet werden, wenn sie 3 benötigen.
Rick