unicode - Stränglängd i byte i JavaScript
I min JavaScript-kod behöver jag komponera ett meddelande till servern i detta format:
<size in bytes>CRLF
<data>CRLF
Exempel :
3
foo
Datan kan innehålla Unicode-tecken. Jag behöver skicka dem som UTF-8.
Jag letar efter det mest webbläsaroberoende sättet att beräkna längden på strängen i byte i JavaScript.
Jag har försökt detta för att komponera min nyttolast:
return unescape(encodeURIComponent(str)).length + "\n" + str + "\n"
Men det ger mig inte exakta resultat för de äldre webbläsarna (eller kanske är strängarna i de webbläsarna i UTF-16?).
Några ledtrådar?
Uppdatering:
Exempel: längden i byte av strängen ЭЭХ! Naiv? i UTF-8 är 15 byte, men vissa webbläsare rapporterar 23 byte istället.
Svar #1
Åren gick och numera kan du göra det på ett naturligt sätt
const textEncoder = new TextEncoder();
console.log(textEncoder.encode('foo').length);Observera att det inte stöds av IE (du kan använda en polyfill för det).
MDN-dokumentation
Standard specifikationer
Svar #2
Det finns inget sätt att göra det i JavaScript nativt. (Se Riccardo Gallis svar för en modern metod.)
För historisk referens eller där TextEncoder API:er fortfarande inte är tillgängliga.
Om du känner till teckenkodningen kan du dock räkna ut det själv.
encodeURIComponent antar UTF-8 som teckenkodning, så om du behöver den kodningen kan du göra,
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);
}
Detta borde fungera på grund av hur UTF-8 kodar flermängdiga byte-sekvenser. Den första kodade byten börjar alltid antingen med en hög bit av noll för en enkel byte-sekvens, eller en byte vars första hexadecimala siffra är C, D, E eller F. De andra och efterföljande byten är de vars första två bitar är 10. Det är dessa extra byten du vill räkna i UTF-8.
Tabellen på Wikipedia gör det tydligare
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
...
Om du istället behöver förstå sidans kodning kan du använda detta trick:
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);
}
Svar #3
För enkel UTF-8-kodning, med något bättre kompatibilitet än TextEncoder, fungerar Blob. Fungerar dock inte i mycket gamla webbläsare.
const blob = new Blob(["😀"]);
console.log(blob.size);Svar #4
Här är en mycket snabbare version, som varken använder reguljära uttryck eller 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;
}
Här är en prestanda-jämförelse.
Det beräknar bara längden i UTF8 för varje Unicode-kodpunkt som returneras av charCodeAt() (baserat på Wikipedias beskrivningar av UTF8 och UTF16-surrogattecken).
Den följer RFC3629 (där UTF-8-tecken är högst 4 byte långa).
Svar #5
Ett annat mycket enkelt tillvägagångssätt med Buffer (endast för NodeJS):
Buffer.byteLength(string, 'utf8')
Buffer.from(string).length
Svar #6
Denna funktion kommer att returnera storleken i byte för vilken UTF-8-sträng som helst du skickar till den.
function byteCount(s) {
return encodeURI(s).split(/%..|./).length - 1;
}
Källa
Svar #7
Jag jämförde några av de metoder som föreslås här i Firefox för hastighet.
Strängen jag använde innehöll följande tecken: 😀œ´®†¥¨ˆøπ¬˚∆˙©ƒ∂ßåΩ≈ç√∫˜µ≤
Alla resultat är genomsnitt av 3 körningar vardera. Tiderna är i millisekunder. Observera att alla URIEncoding-metoder uppträdde på liknande sätt och hade extrema resultat, så jag inkluderade bara en.
Även om det finns vissa variationer beroende på strängens storlek, presterar charCode-metoderna (lovasoa och fuweichin) på samma sätt och är snabbast totalt sett, med fuweichins charCode-metod som den snabbaste. Blob- och TextEncoder-metoderna presterade på liknande sätt som varandra. Generellt var charCode-metoderna ungefär 75% snabbare än Blob- och TextEncoder-metoderna. URIEncoding-metoden var i princip oacceptabel.
Här är resultaten jag fick:
Storlek 6,4 * 10^6 byte:
Lauri Oherd - URIEncoding: 6400000 et: 796
lovasoa - charCode: 6400000 et: 15
fuweichin - charCode2: 6400000 et: 16
simap - Blob: 6400000 et: 26
Riccardo Galli - TextEncoder: 6400000 et: 23
Storlek 19,2 * 10^6 byte: Blob gör på något sätt en konstig sak här.
Lauri Oherd - URIEncoding: 19200000 et: 2322
lovasoa - charCode: 19200000 et: 42
fuweichin - charCode2: 19200000 et: 45
simap - Blob: 19200000 et: 169
Riccardo Galli - TextEncoder: 19200000 et: 70
Storlek 64 * 10^6 byte:
Lauri Oherd - URIEncoding: 64000000 et: 12565
lovasoa - charCode: 64000000 et: 138
fuweichin - charCode2: 64000000 et: 133
simap - Blob: 64000000 et: 231
Riccardo Galli - TextEncoder: 64000000 et: 211
Storlek 192 * 10^6 byte: URIEncoding-metoder fryser webbläsaren vid denna punkt.
lovasoa - charCode: 192000000 et: 754
fuweichin - charCode2: 192000000 et: 480
simap - Blob: 192000000 et: 701
Riccardo Galli - TextEncoder: 192000000 et: 654
Storlek 640 * 10^6 byte:
lovasoa - charCode: 640000000 et: 2417
fuweichin - charCode2: 640000000 et: 1602
simap - Blob: 640000000 et: 2492
Riccardo Galli - TextEncoder: 640000000 et: 2338
Storlek 1280 * 10^6 byte: Blob- och TextEncoder-metoder börjar stöta på begränsningar här.
lovasoa - charCode: 1280000000 et: 4780
fuweichin - charCode2: 1280000000 et: 3177
simap - Blob: 1280000000 et: 6588
Riccardo Galli - TextEncoder: 1280000000 et: 5074
Storlek 1920 * 10^6 byte:
lovasoa - charCode: 1920000000 et: 7465
fuweichin - charCode2: 1920000000 et: 4968
JavaScript error: file:///Users/xxx/Desktop/test.html, line 74: NS_ERROR_OUT_OF_MEMORY:
Här är koden:
function byteLengthURIEncoding(str) {
return encodeURI(str).split(/%..|./).length - 1;
}
function byteLengthCharCode(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;
}
function byteLengthCharCode2(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;
}
function byteLengthBlob(str) {
return new Blob([str]).size;
}
function byteLengthTE(str) {
return (new TextEncoder().encode(str)).length;
}
var sample = "😀œ´®†¥¨ˆøπ¬˚∆˙©ƒ∂ßåΩ≈ç√∫˜µ≤i";
var string = "";
// Adjust multiplier to change length of string.
let mult = 1000000;
for (var i = 0; i < mult; i++) {
string += sample;
}
let t0;
try {
t0 = Date.now();
console.log("Lauri Oherd - URIEncoding: " + byteLengthURIEncoding(string) + " et: " + (Date.now() - t0));
} catch(e) {}
t0 = Date.now();
console.log("lovasoa - charCode: " + byteLengthCharCode(string) + " et: " + (Date.now() - t0));
t0 = Date.now();
console.log("fuweichin - charCode2: " + byteLengthCharCode2(string) + " et: " + (Date.now() - t0));
t0 = Date.now();
console.log("simap - Blob: " + byteLengthBlob(string) + " et: " + (Date.now() - t0));
t0 = Date.now();
console.log("Riccardo Galli - TextEncoder: " + byteLengthTE(string) + " et: " + (Date.now() - t0));