¿Cuántos bytes en una cadena de JavaScript?

97

Tengo una cadena de JavaScript que es de aproximadamente 500K cuando se envía desde el servidor en UTF-8. ¿Cómo puedo saber su tamaño en JavaScript?

Sé que JavaScript usa UCS-2, entonces eso significa 2 bytes por carácter. Sin embargo, ¿depende de la implementación de JavaScript? ¿O en la codificación de la página o tal vez en el tipo de contenido?

Paul Biggar
fuente
Aprox. la respuesta sería length * charsize, por lo que su conjetura es cercana.
glasnt
1
JavaScript moderno, por ejemplo, ES6, no solo usa UCS-2, más detalles aquí: stackoverflow.com/a/46735247/700206
whitneyland

Respuestas:

36

StringLos valores no dependen de la implementación, de acuerdo con la Especificación ECMA-262 3rd Edition , cada carácter representa una sola unidad de 16 bits de texto UTF-16 :

4.3.16 Valor de cadena

Un valor de cadena es un miembro del tipo Cadena y es una secuencia finita ordenada de cero o más valores enteros sin signo de 16 bits.

NOTA Aunque cada valor generalmente representa una sola unidad de 16 bits de texto UTF-16, el idioma no impone restricciones o requisitos a los valores, excepto que sean enteros de 16 bits sin signo.

Christian C. Salvadó
fuente
8
Mi lectura de ese pasaje no implica independencia de implementación.
Paul Biggar
4
UTF-16 no está garantizado, solo el hecho de las cadenas almacenadas como entradas de 16 bits.
bjornl
Solo depende de la implementación con respecto a UTF-16. La descripción de caracteres de 16 bits es universal.
Panzercrisis
1
Creo que internamente Firefox podría incluso usar 1 byte por carácter para algunas cadenas ... blog.mozilla.org/javascript/2014/07/21/…
Michal Charemza
1
UTF-16 explícitamente no está permitido de la forma en que lo leo. Los caracteres UTF-16 pueden tener hasta 4 bytes, pero la especificación dice que "los valores deben ser enteros sin signo de 16 bits". Esto significa que los valores de cadena de JavaScript son un subconjunto de UTF-16; sin embargo, no se permitiría ninguna cadena UTF-16 que utilice caracteres de 3 o 4 bytes.
Whitneyland
71

Esta función devolverá el tamaño en bytes de cualquier cadena UTF-8 que le pase.

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

Fuente

Los motores de JavaScript son libres de usar UCS-2 o UTF-16 internamente. La mayoría de los motores que conozco usan UTF-16, pero cualquiera que sea la elección que hayan hecho, es solo un detalle de implementación que no afectará las características del lenguaje.

Sin embargo, el propio lenguaje ECMAScript / JavaScript expone caracteres de acuerdo con UCS-2, no UTF-16.

Fuente

Lauri Oherd
fuente
9
Úselo en su .split(/%(?:u[0-9A-F]{2})?[0-9A-F]{2}|./)lugar. Su fragmento falla para las cadenas que se codifican en "% uXXXX".
Rob W
Se utiliza para calcular el tamaño en marcos de websocket, proporciona el mismo tamaño para un marco de cadena que las herramientas de desarrollo de Chrome.
user85155
2
Usado para cadenas de JavaScript subidas a s3, s3 muestra exactamente el mismo tamaño [(byteCount (s)) / 1024) .toFixed (2) + "KiB"]
user85155
42

Puedes usar el Blob para obtener el tamaño de la cadena en bytes.

Ejemplos:

console.info(
  new Blob(['😂']).size,                             // 4
  new Blob(['👍']).size,                             // 4
  new Blob(['😂👍']).size,                           // 8
  new Blob(['👍😂']).size,                           // 8
  new Blob(['I\'m a string']).size,                  // 12

  // from Premasagar correction of Lauri's answer for
  // strings containing lone characters in the surrogate pair range:
  // https://stackoverflow.com/a/39488643/6225838
  new Blob([String.fromCharCode(55555)]).size,       // 3
  new Blob([String.fromCharCode(55555, 57000)]).size // 4 (not 6)
);

P Roitto
fuente
2
¡Gracias a Dios por las manchas! Esta probablemente debería ser la respuesta aceptada para los navegadores modernos.
prasanthv
¿Cómo importar Blob en Node.js?
Alexander Mills
4
Ahh, con Node.js usamos Buffer, por ejemploBuffer.from('😂').length
Alexander Mills
19

Pruebe esta combinación con el uso de la función unescape js:

const byteAmount = unescape(encodeURIComponent(yourString)).length

Ejemplo de proceso de codificación completa:

const s  = "1 a ф № @ ®"; //length is 11
const s2 = encodeURIComponent(s); //length is 41
const s3 = unescape(s2); //length is 15 [1-1,a-1,ф-2,№-3,@-1,®-2]
const s4 = escape(s3); //length is 39
const s5 = decodeURIComponent(s4); //length is 11
Kinjeiro
fuente
4
La unescapefunción de JavaScript está obsoleta y no debe usarse para decodificar identificadores uniformes de recursos (URI). Fuente
Lauri Oherd
@LauriOherd Sé que el comentario es antiguo, pero: En esta respuesta, unescapeno se usa para decodificar URI. Se utiliza para convertir %xxsecuencias en caracteres individuales. Como encodeURIComponentcodifica una cadena como UTF-8, que representa unidades de código como su carácter ASCII correspondiente o como una %xxsecuencia, la llamada unescape(encodeURIComponent(...))da como resultado una cadena binaria que contiene la representación UTF-8 de la cadena original. Llamar .lengthcorrectamente da el tamaño en bytes de la cadena codificada como UTF-8.
TS
Y sí ( un) escapeestá en desuso desde 1999, pero todavía está disponible en todos los navegadores ... - Dicho esto, hay buenas razones para desaprobarlo. Básicamente, no hay forma de usarlos correctamente (excepto para codificar / decodificar UTF8 en combinación con en- / decodeURI( Component) - o al menos no conozco ninguna otra aplicación útil para ( un) escape). Y hoy existen mejores alternativas para codificar / decodificar UTF8 ( TextEncoder, etc.)
TS
10

Tenga en cuenta que si está apuntando a node.js puede usar Buffer.from(string).length:

var str = "\u2620"; // => "☠"
str.length; // => 1 (character)
Buffer.from(str).length // => 3 (bytes)
maerics
fuente
7

UTF-8 codifica caracteres utilizando de 1 a 4 bytes por punto de código. Como CMS señaló en la respuesta aceptada, JavaScript almacenará cada carácter internamente usando 16 bits (2 bytes).

Si analiza cada carácter de la cadena a través de un bucle y cuenta el número de bytes utilizados por punto de código, y luego multiplica el recuento total por 2, debe tener el uso de memoria de JavaScript en bytes para esa cadena codificada en UTF-8. Quizás algo como esto:

      getStringMemorySize = function( _string ) {
        "use strict";

        var codePoint
            , accum = 0
        ;

        for( var stringIndex = 0, endOfString = _string.length; stringIndex < endOfString; stringIndex++ ) {
            codePoint = _string.charCodeAt( stringIndex );

            if( codePoint < 0x100 ) {
                accum += 1;
                continue;
            }

            if( codePoint < 0x10000 ) {
                accum += 2;
                continue;
            }

            if( codePoint < 0x1000000 ) {
                accum += 3;
            } else {
                accum += 4;
            }
        }

        return accum * 2;
    }

Ejemplos:

getStringMemorySize( 'I'    );     //  2
getStringMemorySize( '❤'    );     //  4
getStringMemorySize( '𠀰'   );     //  8
getStringMemorySize( 'I❤𠀰' );     // 14
Mac
fuente
7

Estas son 3 formas en las que uso:

  1. TextEncoder ()

    (new TextEncoder().encode("myString")).length)

  2. Gota

    new Blob(["myString"]).size)

  3. Buffer

    Buffer.byteLength("myString", 'utf8'))

Hong Ly
fuente
5

El tamaño de una cadena de JavaScript es

  • Pre-ES6 : 2 bytes por carácter
  • ES6 y posterior: 2 bytes por carácter o 5 o más bytes por carácter

Pre-ES6
Siempre 2 bytes por carácter. UTF-16 no está permitido porque la especificación dice que "los valores deben ser enteros sin signo de 16 bits". Dado que las cadenas UTF-16 pueden usar caracteres de 3 o 4 bytes, violaría el requisito de 2 bytes. Fundamentalmente, aunque UTF-16 no se puede admitir por completo, el estándar requiere que los caracteres de dos bytes utilizados sean caracteres UTF-16 válidos. En otras palabras, las cadenas de JavaScript anteriores a ES6 admiten un subconjunto de caracteres UTF-16.

ES6 y posterior
2 bytes por carácter, o 5 o más bytes por carácter. Los tamaños adicionales entran en juego porque ES6 (ECMAScript 6) agrega soporte para escapes de puntos de código Unicode . El uso de un escape Unicode se ve así: \ u {1D306}

Notas practicas

  • Esto no se relaciona con la implementación interna de un motor en particular. Por ejemplo, algunos motores utilizan estructuras de datos y bibliotecas con compatibilidad total con UTF-16, pero lo que proporcionan externamente no tiene por qué ser compatibilidad total con UTF-16. Además, un motor también puede proporcionar soporte UTF-16 externo, pero no está obligado a hacerlo.

  • Para ES6, prácticamente hablando, los caracteres nunca tendrán más de 5 bytes de longitud (2 bytes para el punto de escape + 3 bytes para el punto de código Unicode) porque la última versión de Unicode solo tiene 136,755 caracteres posibles, que se ajustan fácilmente a 3 bytes. Sin embargo, esto técnicamente no está limitado por el estándar, por lo que, en principio, un solo carácter podría usar, por ejemplo, 4 bytes para el punto de código y 6 bytes en total.

  • La mayoría de los ejemplos de código aquí para calcular el tamaño de byte no parecen tener en cuenta los escapes de puntos de código Unicode ES6, por lo que los resultados podrían ser incorrectos en algunos casos.

Whitneyland
fuente
1
Sólo me preguntaba, si el tamaño es de 2 bytes por carácter, por qué Buffer.from('test').lengthy Buffer.byteLength('test')igual a 4 (en el Nodo) y new Blob(['test']).sizetambién es igual a 4?
user1063287
Pre-ES6: se permite UTF-16: consulte ECMA-262 3ª edición (desde 1999) : La página uno dice que se permite UCS2 o UTF-16. Página 5, definición de valor de cadena: "... Aunque cada valor generalmente representa una sola unidad de 16 bits de texto UTF-16, ...". En la página 81 hay una tabla que muestra cómo los pares sustitutos coincidentes deben codificarse como cuatro bytes UTF-8.
TS
"por carácter": si con eso quiere decir, por "carácter percibido por el usuario" ( especificación , explicación más simple ), podría ser cualquier número de unidades de código de 16 bits. Si quiso decir por "punto de código", puede ser una o dos unidades de código de 16 bits en UTF-16 . (No puede ser de 2,5 unidades de código (¿o cómo se obtienen 5 bytes?))
TS
Si cada elemento en una cadena javascript ( valores enteros sin signo de 16 bits ("elementos") ) está realmente representado internamente por dos bytes no está definido en el estándar. (¿Y cómo podría ser? Siempre que la interfaz provista para el programa javascript siga el estándar, todo funciona según lo previsto). Mozilla, por ejemplo, puede usar solo un byte por punto de código si la cadena solo contiene latin1
TS
Los escapes de puntos de código Unicode no tienen nada que ver con la longitud de la cadena; es solo una nueva forma de representar cadenas en el código fuente. ( '\u{1F600}'.length===2, '\u{1F600}'==='\uD83D\uDE00', '\u{1F600}'==='😀')
TS
3

Un solo elemento en una cadena de JavaScript se considera una sola unidad de código UTF-16. Es decir, los caracteres de las cadenas se almacenan en 16 bits (1 unidad de código) y 16 bits es igual a 2 bytes (8 bits = 1 byte).

El charCodeAt()método se puede utilizar para devolver un número entero entre 0 y 65535 que representa la unidad de código UTF-16 en el índice dado.

los codePointAt() puede utilizar para devolver el valor de punto de código completo para caracteres Unicode, por ejemplo, UTF-32.

Cuando un carácter UTF-16 no se puede representar en una sola unidad de código de 16 bits, tendrá un par sustituto y, por lo tanto, utilizará dos unidades de código (2 x 16 bits = 4 bytes)

Consulte Codificaciones Unicode para conocer las diferentes codificaciones y sus rangos de código.

holmberd
fuente
Lo que dice acerca de los sustitutos parecería violar la especificación del script ECMA. Como comenté anteriormente, la especificación requiere dos bytes por carácter, y permitir pares sustitutos violaría esto.
Whitneyland
Los motores Javascript ES5 son internamente libres de usar USC-2 o UTF-16, pero lo que realmente está usando es una especie de UCS-2 con sustitutos. Esto se debe a que permite exponer mitades sustitutas como caracteres separados, enteros únicos UTF-16 sin signo. Si utiliza un carácter Unicode en su código fuente que necesita más de una unidad de código de 16 bits para ser representada, se utilizará un par sustituto. Este comportamiento no infringe las especificaciones, consulte el texto fuente del capítulo 6: ecma-international.org/ecma-262/5.1
holmberd
2

La respuesta de Lauri Oherd funciona bien para la mayoría de las cadenas que se ven en la naturaleza, pero fallará si la cadena contiene caracteres solitarios en el rango de pares sustitutos, 0xD800 a 0xDFFF. P.ej

byteCount(String.fromCharCode(55555))
// URIError: URI malformed

Esta función más larga debería manejar todas las cadenas:

function bytes (str) {
  var bytes=0, len=str.length, codePoint, next, i;

  for (i=0; i < len; i++) {
    codePoint = str.charCodeAt(i);

    // Lone surrogates cannot be passed to encodeURI
    if (codePoint >= 0xD800 && codePoint < 0xE000) {
      if (codePoint < 0xDC00 && i + 1 < len) {
        next = str.charCodeAt(i + 1);

        if (next >= 0xDC00 && next < 0xE000) {
          bytes += 4;
          i++;
          continue;
        }
      }
    }

    bytes += (codePoint < 0x80 ? 1 : (codePoint < 0x800 ? 2 : 3));
  }

  return bytes;
}

P.ej

bytes(String.fromCharCode(55555))
// 3

Calculará correctamente el tamaño de las cadenas que contienen pares sustitutos:

bytes(String.fromCharCode(55555, 57000))
// 4 (not 6)

Los resultados se pueden comparar con la función incorporada de Node Buffer.byteLength:

Buffer.byteLength(String.fromCharCode(55555), 'utf8')
// 3

Buffer.byteLength(String.fromCharCode(55555, 57000), 'utf8')
// 4 (not 6)
Premasagar
fuente
1

Estoy trabajando con una versión integrada del motor V8. Probé una sola cuerda. Empujando cada paso 1000 caracteres. UTF-8.

Primera prueba con el carácter "A" (hexadecimal: 41) de un solo byte (8 bits, ANSI). Segunda prueba con carácter de dos bytes (16 bits) "Ω" (hexadecimal: CE A9) y la tercera prueba con carácter de tres bytes (24 bits) "☺" (hexadecimal: E2 98 BA).

En los tres casos, el dispositivo imprime sin memoria a 888 000 caracteres y utilizando aprox. 26 348 kb en RAM.

Resultado: los caracteres no se almacenan dinámicamente. Y no con solo 16 bits. - Ok, quizás solo para mi caso (Dispositivo de RAM de 128 MB integrado, motor V8 C ++ / QT) - La codificación de caracteres no tiene nada que ver con el tamaño en RAM del motor javascript. Por ejemplo, encodingURI, etc. solo es útil para la transmisión y el almacenamiento de datos de alto nivel.

Embebidos o no, lo cierto es que los caracteres no solo se almacenan en 16bit. Desafortunadamente, no tengo una respuesta del 100%, lo que Javascript hace en el área de bajo nivel. Por cierto. He probado lo mismo (primera prueba anterior) con una matriz de caracteres "A". Empujó 1000 elementos en cada paso. (Exactamente la misma prueba. Reemplazo de cadena a matriz) Y el sistema saca de memoria (deseado) después de 10 416 KB usando una longitud de matriz de 1 337 000. Entonces, el motor javascript no es simplemente restringido. Es un tipo más complejo.

Dominik
fuente
0

Puedes probar esto:

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

Funcionó para mí.

usuario3728331
fuente
1
¿Seguramente esto supone que todos los caracteres tienen un máximo de 2 bytes? Si hay caracteres de 3 o 4 bytes (que son posibles en UTF-8), ¿esta función solo los contará como caracteres de 2 bytes?
Adam Burley