web-dev-qa-db-fra.com

Longueur de la chaîne en octets en JavaScript

Dans mon code JavaScript, je dois composer un message au serveur dans ce format:

<size in bytes>CRLF
<data>CRLF

Exemple:

3
foo

Les données peuvent contenir des caractères unicode. Je dois les envoyer au format UTF-8.

Je cherche le moyen le plus multi-navigateur pour calculer la longueur de la chaîne en octets en JavaScript.

J'ai essayé ceci pour composer ma charge utile:

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

Mais cela ne me donne pas des résultats précis pour les anciens navigateurs (ou, peut-être, les chaînes de ces navigateurs dans UTF-16?).

Des indices?

Mise à jour:

Exemple: longueur en octets de la chaîne ЭЭХ! Naïve? en UTF-8 correspond à 15 octets, mais certains navigateurs signalent plutôt 23 octets.

79

Il n'y a aucun moyen de le faire en JavaScript de manière native. (Voir réponse de Riccardo Galli pour une approche moderne.)


Pour référence historique ou où les API TextEncoder sont toujours indisponible .

Si vous connaissez l'encodage des caractères, vous pouvez le calculer vous-même.

encodeURIComponent suppose que UTF-8 est l'encodage de caractères, donc si vous avez besoin de cet encodage, vous pouvez le faire,

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

Cela devrait fonctionner à cause de la façon dont UTF-8 code pour les séquences multi-octets. Le premier octet codé commence toujours par un bit haut de zéro pour une séquence d'octets unique ou par un octet dont le premier chiffre hexadécimal est C, D, E ou F. Les octets suivants sont ceux dont les deux premiers bits sont 10. Ce sont les octets supplémentaires que vous voulez compter dans UTF-8.

Le tableau dans wikipedia le rend plus clair

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

Si vous devez au contraire comprendre le codage de la page, vous pouvez utiliser cette astuce:

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);
}
79
Mike Samuel

Les années ont passé et aujourd'hui, vous pouvez le faire en natif

(new TextEncoder('utf-8').encode('foo')).length

Notez que ce n'est pas encore supporté par IE (ou Edge) (vous pouvez tilisez un polyfill pour cela).

documentation MDN

Spécifications standard

78
Riccardo Galli

Voici une version beaucoup plus rapide, qui n’utilise ni expressions régulières, ni 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;
}

Voici une comparaison performance .

Il calcule simplement la longueur en UTF8 de chaque code codé Unicode renvoyé par charCodeAt () (basé sur les descriptions par wikipedia de TF8 et de caractères de substitution UTF16).

Il s'ensuit RFC3629 (où les caractères UTF-8 ont une longueur d'au plus 4 octets).

56
lovasoa

Pour l'encodage UTF-8 simple, avec une compatibilité légèrement meilleure que TextEncoder, Blob fait l'affaire. Ne fonctionnera pas dans les très vieux navigateurs cependant.

new Blob(["????"]).size; // -> 4  
38
simap

Cette fonction renvoie la taille en octets de toute chaîne UTF-8 que vous lui transmettez.

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

Source

29
Lauri Oherd

Une autre approche très simple utilisant Buffer (uniquement pour NodeJS):

Buffer.from(string).length
13
Iván Pérez

En fait, j'ai compris ce qui ne va pas. Pour que le code fonctionne la page <head> devrait avoir cette balise:

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

Ou, comme suggéré dans les commentaires, si le serveur envoie HTTP Content-Encoding en-tête, cela devrait également fonctionner.

Ensuite, les résultats de différents navigateurs sont cohérents.

Voici un exemple:

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

Remarque: je soupçonne que spécifier n'importe lequel (exact) encodage résoudrait le problème de codage. Ce n'est que par coïncidence que j'ai besoin de UTF-8.

4

Il m'a fallu un certain temps pour trouver une solution pour React Native alors je vais la mettre ici:

Commencez par installer le paquet buffer:

npm install --save buffer

Puis utilisez la méthode du noeud:

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

Voici une méthode indépendante et efficace pour compter les octets UTF-8 d'une chaîne.

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

Note Que la méthode peut générer une erreur si une chaîne d'entrée est mal formée par UCS-2

3
fuweichin

Dans NodeJS, Buffer.byteLength est une méthode spécialement conçue à cet effet:

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

Notez que par défaut, la méthode suppose que la chaîne est en codage UTF-8. Si un autre codage est requis, transmettez-le comme second argument.

1
Boaz

Cela fonctionnerait pour les caractères BMP et 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. 
1
chrislau

Vous pouvez essayer ceci:

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

Ça marche pour moi.

0
anh tran