Quantos bytes em uma string JavaScript?

99

Eu tenho uma string de javascript que tem cerca de 500 K quando enviada do servidor em UTF-8. Como posso saber seu tamanho em JavaScript?

Eu sei que JavaScript usa UCS-2, então isso significa 2 bytes por caractere. No entanto, isso depende da implementação do JavaScript? Ou na codificação da página ou talvez no tipo de conteúdo?

Paul Biggar
fonte
Aproximadamente. a resposta seria length * charsize, então seu palpite está próximo.
glasnt
1
JavaScript moderno, por exemplo ES6, não usa apenas UCS-2, mais detalhes aqui: stackoverflow.com/a/46735247/700206
whitneyland

Respostas:

37

Stringos valores não são dependentes da implementação, de acordo com a Especificação ECMA-262 3ª Edição , cada caractere representa uma única unidade de 16 bits de texto UTF-16 :

4.3.16 String Value

Um valor de string é um membro do tipo String e é uma sequência ordenada finita de zero ou mais valores inteiros não assinados de 16 bits.

OBSERVAÇÃO Embora cada valor geralmente represente uma única unidade de 16 bits de texto UTF-16, o idioma não impõe quaisquer restrições ou requisitos aos valores, exceto que eles são inteiros sem sinal de 16 bits.

Christian C. Salvadó
fonte
8
Minha leitura dessa passagem não implica independência de implementação.
Paul Biggar
4
UTF-16 não é garantido, apenas o fato das strings armazenadas como ints de 16 bits.
bjornl
Depende apenas da implementação em relação ao UTF-16. A descrição do caractere de 16 bits é universal.
Panzercrisis
1
Acho que internamente o Firefox poderia até usar 1 byte por caractere para algumas strings .... blog.mozilla.org/javascript/2014/07/21/…
Michal Charemza
1
UTF-16 é explicitamente proibido da maneira como estou lendo. Os caracteres UTF-16 podem ter até 4 bytes, mas a especificação diz "os valores devem ser inteiros sem sinal de 16 bits". Isso significa que os valores de string JavaScript são um subconjunto de UTF-16; no entanto, qualquer string UTF-16 usando caracteres de 3 ou 4 bytes não seria permitida.
Whitneyland
71

Esta função retornará o tamanho de byte de qualquer string UTF-8 que você passar para ela.

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

Fonte

Os mecanismos JavaScript são gratuitos para usar o UCS-2 ou UTF-16 internamente. A maioria dos mecanismos que conheço usa UTF-16, mas seja qual for a escolha que eles fizeram, é apenas um detalhe de implementação que não afetará as características da linguagem.

A própria linguagem ECMAScript / JavaScript, no entanto, expõe caracteres de acordo com UCS-2, não UTF-16.

Fonte

Lauri Oherd
fonte
9
Use em seu .split(/%(?:u[0-9A-F]{2})?[0-9A-F]{2}|./)lugar. Seu snippet falha para strings que codificam para "% uXXXX".
Rob W
Usado para cálculo de tamanho em frames de websocket, dá o mesmo tamanho para um frame String que as ferramentas de desenvolvimento de cromo.
user85155
2
Usado para strings javascript enviadas para s3, s3 exibe exatamente o mesmo tamanho [(byteCount (s)) / 1024) .toFixed (2) + "KiB"]
user85155
46

Você pode usar o Blob para obter o tamanho da string em bytes.

Exemplos:

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
fonte
3
Graças a Deus por bolhas! Essa provavelmente deve ser a resposta aceita para navegadores modernos.
prasanthv
como importar Blob em Node.js?
Alexander Mills
5
Ahh, com Node.js usamos Buffer, por exemploBuffer.from('😂').length
Alexander Mills
19

Experimente esta combinação usando a função unescape js:

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

Exemplo de processo de codificação 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
fonte
4
A unescapefunção JavaScript está obsoleta e não deve ser usada para decodificar Uniform Resource Identifiers (URI). Fonte
Lauri Oherd
@LauriOherd Eu sei que o comentário é antigo, mas: Nesta resposta, unescapenão é usado, para decodificar URIs. É usado para converter %xxsequências em caracteres únicos. O As encodeURIComponentcodifica uma string como UTF-8, representando codeunits como seu caractere ASCII correspondente ou como uma %xxsequência, chamando os unescape(encodeURIComponent(...))resultados em uma string binária contendo a representação UTF-8 da string original. Chamar .lengthcorretamente fornece o tamanho em bytes da string codificada como UTF-8.
TS
E yes ( un) escapeestá obsoleto desde 1999, mas ainda está disponível em todos os navegadores ... - Dito isso, há um bom motivo para ele. Basicamente, não há maneira de usá-los corretamente (exceto para en- / decoding UTF8 em combinação com en- / decodeURI( Component) - ou pelo menos não conheço nenhum outro aplicativo útil para ( un) escape). E hoje existem alternativas melhores para codificar / decodificar UTF8 ( TextEncoder, etc.)
TS
10

Observe que se você está direcionando o node.js, pode usar Buffer.from(string).length:

var str = "\u2620"; // => "☠"
str.length; // => 1 (character)
Buffer.from(str).length // => 3 (bytes)
matemática
fonte
9

Estas são as 3 maneiras que eu uso:

  1. TextEncoder
new TextEncoder().encode("myString").length
  1. Blob
new Blob(["myString"]).size
  1. Buffer
Buffer.byteLength("myString", 'utf8')
Hong Ly
fonte
FYI: Parece que todos os segmentos de código têm um parêntese de fechamento desemparelhado no final.
SamWN
Parece que a TextEncoderopção é mais de 6 vezes mais rápida: i.ibb.co/QkfsJQN/Screenshot-from-2020-12-20-16-29-27.png
Saiansh Singh
7

UTF-8 codifica caracteres usando 1 a 4 bytes por ponto de código. Como o CMS apontou na resposta aceita, o JavaScript armazenará cada caractere internamente usando 16 bits (2 bytes).

Se você analisar cada caractere na string por meio de um loop e contar o número de bytes usados ​​por ponto de código e, em seguida, multiplicar a contagem total por 2, deverá ter o uso de memória JavaScript em bytes para essa string codificada em UTF-8. Talvez algo assim:

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

Exemplos:

getStringMemorySize( 'I'    );     //  2
getStringMemorySize( '❤'    );     //  4
getStringMemorySize( '𠀰'   );     //  8
getStringMemorySize( 'I❤𠀰' );     // 14
Mac
fonte
6

O tamanho de uma string JavaScript é

  • Pré-ES6 : 2 bytes por caractere
  • ES6 e posterior: 2 bytes por caractere ou 5 ou mais bytes por caractere

Pré-ES6
Sempre 2 bytes por caractere. UTF-16 não é permitido porque a especificação diz "os valores devem ser inteiros sem sinal de 16 bits". Como as strings UTF-16 podem usar caracteres de 3 ou 4 bytes, isso violaria o requisito de 2 bytes. Crucialmente, enquanto UTF-16 não pode ser totalmente suportado, o padrão requer que os dois caracteres de byte usados ​​sejam caracteres UTF-16 válidos. Em outras palavras, as sequências JavaScript Pré-ES6 suportam um subconjunto de caracteres UTF-16.

ES6 e posterior
2 bytes por caractere ou 5 ou mais bytes por caractere. Os tamanhos adicionais entram em jogo porque o ES6 (ECMAScript 6) adiciona suporte para escapes de ponto de código Unicode . O uso de um escape Unicode tem a seguinte aparência: \ u {1D306}

Notas práticas

  • Isso não se relaciona à implementação interna de um motor específico. Por exemplo, alguns mecanismos usam estruturas de dados e bibliotecas com suporte total a UTF-16, mas o que eles fornecem externamente não precisa ser suporte total a UTF-16. Além disso, um motor pode fornecer suporte UTF-16 externo, mas não é obrigado a fazê-lo.

  • Para ES6, os caracteres praticamente falando nunca terão mais de 5 bytes de comprimento (2 bytes para o ponto de escape + 3 bytes para o ponto de código Unicode) porque a versão mais recente do Unicode tem apenas 136.755 caracteres possíveis, que se encaixam facilmente em 3 bytes. No entanto, isso não é tecnicamente limitado pelo padrão, portanto, em princípio, um único caractere poderia usar, digamos, 4 bytes para o ponto de código e 6 bytes no total.

  • A maioria dos exemplos de código aqui para calcular o tamanho do byte não parecem levar em conta os escapes de ponto de código ES6 Unicode, portanto, os resultados podem estar incorretos em alguns casos.

Whitneyland
fonte
1
Basta saber, se o tamanho é de 2 bytes por caractere, por que Buffer.from('test').lengthe Buffer.byteLength('test')igual a 4 (em Node) e new Blob(['test']).sizetambém é igual a 4?
user1063287
Pré-ES6: UTF-16 é permitido: Consulte ECMA-262 3ª edição (de 1999) : A página um diz UCS2 ou UTF-16 são permitidos. Página 5, definição do valor da string: "... Embora cada valor geralmente represente uma única unidade de 16 bits de texto UTF-16, ...". Na página 81, há uma tabela que mostra como os pares substitutos correspondentes devem ser codificados como quatro bytes UTF-8.
TS
"por caractere" - Se com isso você quer dizer, por "caractere percebido pelo usuário" ( especificação , explicação mais simples ) pode ser qualquer número de unidades de código de 16 bits. Se você quis dizer por "ponto de código", pode ser uma ou duas unidades de código de 16 bits em UTF-16 . (Não pode ser 2,5 unidades de código (ou como você obtém 5 bytes?))
TS de
Se cada elemento em uma string javascript ( valores inteiros sem sinal de 16 bits (“elementos”) ) é realmente representado internamente por dois bytes, não é definido no padrão. (E como poderia ser - contanto que a interface fornecida para o programa javascript siga o padrão, tudo funciona como pretendido.) O Mozilla, por exemplo, pode usar apenas um byte por ponto de código se a string contiver apenas latin1
TS
Os escapes do ponto de código Unicode não têm nada a ver com o comprimento da string - é apenas uma nova maneira de representar strings no código-fonte. ( '\u{1F600}'.length===2, '\u{1F600}'==='\uD83D\uDE00', '\u{1F600}'==='😀')
TS
3

Um único elemento em uma String JavaScript é considerado uma única unidade de código UTF-16. Ou seja, os caracteres Strings são armazenados em 16 bits (1 unidade de código) e 16 bits é igual a 2 bytes (8 bits = 1 byte).

O charCodeAt()método pode ser usado para retornar um número inteiro entre 0 e 65535 que representa a unidade de código UTF-16 no índice fornecido.

O codePointAt()pode ser usado para retornar o valor de ponto de código inteiro para caracteres Unicode, por exemplo, UTF-32.

Quando um caractere UTF-16 não pode ser representado em uma única unidade de código de 16 bits, ele terá um par substituto e, portanto, usará duas unidades de código (2 x 16 bits = 4 bytes)

Consulte codificações Unicode para codificações diferentes e seus intervalos de código.

Holmberd
fonte
O que você diz sobre substitutos parece violar as especificações do script ECMA. Como comentei acima, a especificação requer dois bytes por caractere, e permitir pares substitutos violaria isso.
Whitneyland
Os motores Javascript ES5 são internamente gratuitos para usar USC-2 ou UTF-16, mas o que realmente está usando é uma espécie de UCS-2 com substitutos. Isso porque ele permite expor metades substitutas como caracteres separados, inteiros UTF-16 únicos sem sinal. Se você usar um caractere Unicode em seu código-fonte que precisa de mais de uma unidade de código de 16 bits para ser representado, um par substituto será usado. Este comportamento não viola as especificações, consulte o texto fonte do capítulo 6: ecma-international.org/ecma-262/5.1
holmberd
2

A resposta de Lauri Oherd funciona bem para a maioria das strings vistas em estado selvagem, mas falhará se a string contiver caracteres solitários no intervalo do par substituto, 0xD800 a 0xDFFF. Por exemplo

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

Esta função mais longa deve lidar com todas as strings:

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

Por exemplo

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

Ele calculará corretamente o tamanho das strings que contêm pares substitutos:

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

Os resultados podem ser comparados com a função integrada do Node Buffer.byteLength:

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

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

Estou trabalhando com uma versão incorporada do V8 Engine. Eu testei uma única corda. Empurrando cada etapa 1000 caracteres. UTF-8.

Primeiro teste com caractere "A" de byte único (8 bits, ANSI) (hex: 41). Segundo teste com caracteres de dois bytes (16 bits) "Ω" (hex: CE A9) e o terceiro teste com caracteres de três bytes (24 bits) "☺" (hex: E2 98 BA).

Em todos os três casos, o dispositivo imprime sem memória em 888.000 caracteres e usando ca. 26 348 kb na RAM.

Resultado: os caracteres não são armazenados dinamicamente. E não com apenas 16 bits. - Ok, talvez apenas para o meu caso (Dispositivo embutido de 128 MB de RAM, V8 Engine C ++ / QT) - A codificação de caracteres não tem nada a ver com o tamanho da memória RAM do mecanismo javascript. Por exemplo, encodingURI, etc. só é útil para transmissão e armazenamento de dados de alto nível.

Incorporados ou não, o fato é que os personagens não são armazenados apenas em 16 bits. Infelizmente não tenho resposta 100%, o que Javascript faz em área de baixo nível. Btw. Eu testei o mesmo (primeiro teste acima) com uma matriz de caracteres "A". Empurrou 1000 itens a cada etapa. (Exatamente o mesmo teste. Apenas substituiu a string por array) E o sistema fica sem memória (desejado) após 10 416 KB usando um comprimento de array de 1 337 000. Portanto, o mecanismo de javascript não é simplesmente restrito. É um pouco mais complexo.

Dominik
fonte
0

Você pode tentar isto:

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

Funcionou para mim

user3728331
fonte
1
Certamente isso assume que todos os caracteres têm no máximo 2 bytes? Se houver caracteres de 3 ou 4 bytes (que são possíveis em UTF-8), esta função irá contá-los apenas como caracteres de 2 bytes?
Adam Burley