Longueur de chaîne en octets en JavaScript

104

Dans mon code JavaScript, j'ai besoin de 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 en UTF-8.

Je recherche le moyen le plus polyvalent 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 de résultats précis pour les anciens navigateurs (ou peut-être les chaînes de ces navigateurs en UTF-16?).

Des indices?

Mettre à jour:

Exemple: la longueur en octets de la chaîne ЭЭХ! Naïve?en UTF-8 est de 15 octets, mais certains navigateurs rapportent 23 octets à la place.

Alexandre Gladysh
la source
1
Duplicata possible? stackoverflow.com/questions/2219526/…
Eli
@Eli: aucune des réponses à la question que vous avez liée ne fonctionne pour moi.
Alexander Gladysh
Quand vous parlez de "ЭЭХ! Naïf?" l'avez-vous mis sous une forme normale particulière? unicode.org/reports/tr15
Mike Samuel
@Mike: Je l'ai tapé dans l'éditeur de texte aléatoire (en mode UTF-8) et l'ai sauvegardé. Tout comme le ferait n'importe quel utilisateur de ma bibliothèque. Cependant, il semble que j'ai compris ce qui n'allait pas - voir ma réponse.
Alexander Gladysh

Réponses:

89

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


Pour référence historique ou lorsque les API TextEncoder ne sont toujours pas disponibles .

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

encodeURIComponent suppose UTF-8 comme encodage de caractères, donc si vous avez besoin de cet encodage, vous pouvez 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 en raison de la façon dont UTF-8 encode les séquences multi-octets. Le premier octet codé commence toujours par soit un bit haut de zéro pour une séquence à un octet, soit un octet dont le premier chiffre hexadécimal est C, D, E ou F.Le deuxième octet et les suivants sont ceux dont les deux premiers bits sont 10 Ce sont les octets supplémentaires que vous voulez compter en UTF-8.

La table 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 à la place vous avez besoin de comprendre l'encodage 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);
}
Mike Samuel
la source
Eh bien, comment pourrais-je connaître le codage des caractères des données? J'ai besoin d'encoder n'importe quelle chaîne utilisateur (programmeur) fournie à ma bibliothèque JS.
Alexander Gladysh
@Alexander, lorsque vous envoyez le message au serveur, spécifiez-vous le codage du contenu du corps du message via un en-tête HTTP?
Mike Samuel
1
@Alexander, cool. Si vous établissez un protocole, rendre obligatoire UTF-8 est une excellente idée pour l'échange de texte. Une variable de moins qui peut entraîner une discordance. UTF-8 doit être l'ordre d'octets réseau des encodages de caractères.
Mike Samuel
4
@MikeSamuel: La lengthInUtf8Bytesfonction renvoie 5 pour les caractères non BMP comme str.lengthpour ces retours 2. J'écrirai une version modifiée de cette fonction dans la section des réponses.
Lauri Oherd
1
Cette solution est cool mais utf8mb4 n'est pas envisagée. Par exemple, encodeURIComponent('🍀')est '%F0%9F%8D%80'.
albert
117

Les années ont passé et de nos jours vous pouvez le faire nativement

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

Notez qu'il n'est pas encore pris en charge par IE (ou Edge) (vous pouvez utiliser un polyfill pour cela).

Documentation MDN

Spécifications standard

Riccardo Galli
la source
4
Quelle approche fantastique et moderne. Merci!
Con Antonakos
Notez que selon la documentation MDN , TextEncoder n'est pas encore pris en charge par Safari (WebKit).
Maor
TextEncodeprend en charge uniquement utf-8 depuis Chrome 53.
Jehong Ahn
1
Si vous n'avez besoin que de la longueur, il peut être exagéré d'allouer une nouvelle chaîne, d'effectuer la conversion réelle, de prendre la longueur, puis de supprimer la chaîne. Voir ma réponse ci-dessus pour une fonction qui calcule simplement la longueur de manière efficace.
lovasoa
66

Voici une version beaucoup plus rapide, qui n'utilise pas d'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 des performances .

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

Il suit la RFC3629 (où les caractères UTF-8 font au plus 4 octets de long).

Lovasoa
la source
46

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

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

Cette fonction renverra la taille d'octet de toute chaîne UTF-8 que vous lui passez.

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

La source

Lauri Oherd
la source
cela ne fonctionne pas avec la chaîne 'ユ ー ザ ー コ ー ド', longueur prévue de 14 mais 21
May Weather VN
1
@MayWeatherVN votre mauvaise ユーザーコードlongueur en octets est toujours de 21, je l'ai testé sur différents outils; soyez plus gentil avec vos commentaires;)
Capitex
Cette chaîne dont je me souviens avoir testé sur php est le 14
May Weather VN
23

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

Buffer.byteLength(string, 'utf8')

Buffer.from(string).length
Iván Pérez
la source
1
Vous pouvez ignorer la création d'un tampon avec Buffer.byteLength(string, 'utf8').
Joe le
1
@Joe Merci pour la suggestion, je viens de faire une modification pour l'inclure.
Iván Pérez le
5

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

Installez d'abord le bufferpackage:

npm install --save buffer

Ensuite, utilisez la méthode du nœud:

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

En fait, j'ai compris ce qui ne va pas. Pour que le code fonctionne, la page <head>doit 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 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 la spécification d' un encodage (précis) résoudrait le problème d'encodage. C'est juste une coïncidence que j'ai besoin d'UTF-8.

Alexandre Gladysh
la source
2
La unescapefonction JavaScript ne doit pas être utilisée pour décoder des URI (Uniform Resource Identifiers).
Lauri Oherd
1
@LauriOherd unescapene doit en effet jamais être utilisé pour décoder les URI. Cependant, pour convertir du texte en UTF-8, cela fonctionne bien
TS
unescape(encodeURIComponent(...)).lengthcalcule toujours la longueur correcte avec ou sans meta http-equiv ... utf8. Sans une spécification de codage, certains navigateurs pourraient simplement avoir un texte différent (après avoir codé les octets du document en texte html réel) dont ils ont calculé la longueur. On pourrait tester cela facilement, en imprimant non seulement la longueur, mais aussi le texte lui-même.
TS
3

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

Notez que la méthode peut générer une erreur si une chaîne d'entrée est malformée UCS-2

Fuweichin
la source
3

Dans NodeJS, Buffer.byteLengthest une méthode spécifiquement à 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 codage différent est requis, passez-le comme deuxième argument.

Boaz
la source
Est-il possible de calculer strLengthInBytessimplement en connaissant le «nombre» de caractères dans la chaîne? ie var text = "Hello World!; var text_length = text.length; // pass text_length as argument to some method?. Et, juste pour la référence, re Buffer- Je viens suis tombé sur cette réponse qui traite new Blob(['test string']).sizeet, dans le nœud, Buffer.from('test string').length. Peut-être que cela aidera certaines personnes aussi?
user1063287
1
@ user1063287 Le problème est que le nombre de caractères n'est pas toujours équivalent au nombre d'octets. Par exemple, le codage UTF-8 commun est un codage à largeur variable, dans lequel un seul caractère peut avoir une taille de 1 à 4 octets. C'est pourquoi une méthode spéciale est nécessaire ainsi que l'encodage utilisé.
Boaz
Par exemple, une chaîne UTF-8 avec 4 caractères, peut avoir au moins 4 octets "long", si chaque caractère est juste 1 octet; et au plus 16 octets de "long" si chaque caractère est de 4 octets. Notez que dans les deux cas, le nombre de caractères est toujours de 4 et n'est donc pas une mesure fiable de la longueur d'octets .
Boaz
1

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. 
Chrislau
la source
0

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.

anh tran
la source
renvoie 1 pour "â" en chrome
Rick
le premier problème pourrait être résolu en changeant \ xff en \ x7f, mais cela ne résout pas le fait que les points de code entre 0x800-0xFFFF seront signalés comme prenant 2 octets, lorsqu'ils prennent 3.
Rick