JavaScript字符串有多少个字节?

143

我有一个javascript字符串,当以UTF-8从服务器发送时,它的大小约为500K。如何在JavaScript中确定它的大小?

我知道JavaScript使用UCS-2,所以每个字符占2个字节。但是,这是否取决于JavaScript的实现方式?还是取决于页面编码或内容类型?


大约的答案应该是长度*字符大小,所以你的猜测很接近。 - glasnt
1
现代JavaScript,例如ES6,并不仅使用UCS-2,更多细节请参见此处:https://dev59.com/JnE95IYBdhLWcg3wp_kg#46735247 - whitneyland
14个回答

104
你可以使用Blob来获取字符串的字节大小。
示例:

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://dev59.com/JnE95IYBdhLWcg3wp_kg#39488643
  new Blob([String.fromCharCode(55555)]).size,       // 3
  new Blob([String.fromCharCode(55555, 57000)]).size // 4 (not 6)
);


如何在Node.js中导入Blob? - Alexander Mills
14
啊,使用Node.js时我们会用到Buffer,例如Buffer.from('').length - Alexander Mills
1
Blob现在已经内置于NodeJS v18中。 - Endless

86

这个函数将返回你传递给它的任何UTF-8字符串的字节大小。

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

源代码

JavaScript引擎在内部可以使用UCS-2或UTF-16。我所知道的大多数引擎都使用UTF-16,但无论他们做出了什么选择,这只是一个不会影响语言特性的实现细节。

然而,ECMAScript / JavaScript语言本身根据UCS-2公开字符,而不是UTF-16。

来源


10
请使用.split(/%(?:u[0-9A-F]{2})?[0-9A-F]{2}|./)代替。您的代码片段无法正确处理编码为“%uXXXX”格式的字符串。 - Rob W
用于计算 WebSocket 帧大小,对于字符串帧与 Chrome 开发工具给出相同的大小。 - user85155
3
用于将 JavaScript 字符串上传到 S3,S3 显示完全相同的大小 [ (byteCount(s))/ 1024).toFixed(2) + " KiB" ]。 - user85155

73

如果你正在使用 node.js,可以使用 buffers 来实现更简单的解决方案:

function getBinarySize(string) {
    return Buffer.byteLength(string, 'utf8');
}

有一个npm库可以实现这个功能:https://www.npmjs.org/package/utf8-binary-cutter(来自您真诚的)


这个代码片段返回 5 用于 "\x80\u3042",而 Ruby 的 bytesize 返回 4(请参阅 https://apidock.com/ruby/String/bytesize)。 - Micael Levi
1
@MicaelLevi 你好,虽然我不是Ruby的专家,但可能JavaScript和Ruby在内部对字符串进行编码的方式不同。请参考其他回答此问题的内容:Ruby必须使用UTF-8,而JavaScript似乎使用UCS-2。 - Offirmo
1
不再需要使用Buffer了。Blob和TextEncoder已经内置,而且更加跨平台友好。 - Endless

41

String值不受实现的影响,根据ECMA-262第三版规格说明,每个字符代表着UTF-16文本的一个单一的16位单元:

4.3.16 字符串值

字符串值是String类型的成员,是一个有限的、有序的零或多个16位无符号整数值的序列。

注意:尽管每个值通常表示UTF-16文本的一个单一的16位单元,但语言并未对这些值作出任何限制或要求,除了必须是16位无符号整数。


9
我读过那段文字,但并不意味着实现的独立性。 - Paul Biggar
4
UTF-16并不是一定能够保证的,仅仅只是字符串以16位整数存储的事实。 - bjornl
只有在涉及UTF-16时才依赖于具体实现。16位字符描述是通用的。 - Panzercrisis
1
我认为在某些字符串中,Firefox 内部甚至可以使用每个字符 1 字节的方式。更多详情请参考:https://blog.mozilla.org/javascript/2014/07/21/slimmer-and-faster-javascript-strings-in-firefox/ - Michal Charemza
1
根据我的理解,“UTF-16明确不被允许”。UTF-16字符最多可以有4个字节,但规范中指出“值必须是16位无符号整数”。这意味着JavaScript字符串值是UTF-16的子集,然而,任何使用3或4个字节字符的UTF-16字符串都是不被允许的。 - whitneyland
据我所知,UTF-16字符不能为3个字节,只能是2个或4个。 - BlackVegetable

28

以下是我使用的三种方式:

  1. TextEncoder
new TextEncoder().encode("myString").length
  1. Blob
new Blob(["myString"]).size
  1. 缓冲区
Buffer.byteLength("myString", 'utf8')

FYI:看起来代码段末尾都有一个未配对的闭括号。 - Sam
2
似乎TextEncoder选项快了6倍以上:https://i.ibb.co/QkfsJQN/Screenshot-from-2020-12-20-16-29-27.png - Saiansh Singh
1
缓冲区因跨环境原因而变得最糟糕。 - Endless

22

尝试使用 unescape JavaScript 函数的这种组合:

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

完整的编码过程示例:

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

4
JavaScriptзљ„unescapeе‡Ңж•°е·Із»Џиұ«еәѓз”ЁпәЊдёҚеғ”иҮӨз”ЁдғҺи§Ә码统一资жғђж ‡иҮ†з¬¦пә€URIпә‰гЂ‚жқӨжғђ - Lauri Oherd
1
@LauriOherd 我知道这条评论已经很旧了,但是:在这个答案中,unescape没有被用来解码URI。它被用来将%xx序列转换为单个字符。由于encodeURIComponent将字符串编码为UTF-8,表示代码单元作为其对应的ASCII字符或%xx序列,调用unescape(encodeURIComponent(...))会得到一个二进制字符串,其中包含原始字符串的UTF-8表示。调用.length可以正确地给出以UTF-8编码的字符串的字节大小。 - T S
1
是的,自1999年以来un escape已被弃用,但它仍然在每个浏览器中可用... - 也就是说,有很好的理由将其弃用。基本上没有正确使用它们的方法(除了与en-/decodeURI(Component)结合使用对UTF8进行编码/解码之外-或者至少我不知道其他有用的应用程序(un)escape)。今天有更好的替代方案来编码/解码UTF8(TextEncoder等)。 - T S

14

请注意,如果你的目标是node.js,你可以使用Buffer.from(string).length

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

10

JavaScript字符串的大小是多少

  • ES6之前: 每个字符2字节
  • ES6及其后续版本: 每个字符2字节,或每个字符5个或更多字节

ES6之前
始终为每个字符2字节。不允许使用UTF-16,因为规范中说“值必须是16位无符号整数”。由于UTF-16字符串可以使用3个或4个字节的字符,这将违反2字节要求。关键是,虽然不能完全支持UTF-16,但标准确实要求使用的两个字节字符是有效的UTF-16字符。换句话说,ES6之前的JavaScript字符串支持UTF-16字符的子集。

ES6及其后续版本
每个字符2个字节,或每个字符5个或更多字节。额外的大小是因为ES6(ECMAScript 6)添加了对Unicode代码点转义的支持。使用Unicode转义如下:\u{1D306}

实用笔记

  • 这与特定引擎的内部实现无关。例如,某些引擎使用具有完全UTF-16支持的数据结构和库,但它们提供的外部内容不必是完全的UTF-16支持。此外,引擎也可以提供外部UTF-16支持,但不一定要这样做。

  • 对于ES6,实际上字符永远不会超过5个字节(2个字节用于转义点+3个字节用于Unicode代码点),因为最新版本的Unicode只有136,755个可能的字符,可以轻松地容纳在3个字节中。但是,从技术上讲,这不受标准的限制,因此一个单一字符可以使用4个字节的代码点和6个字节的总长度。

  • 这里大多数计算字节大小的代码示例似乎没有考虑到ES6 Unicode代码点转义,因此在某些情况下结果可能不正确。


6
请问,如果每个字符使用2个字节的大小,为什么在Node中Buffer.from('test').lengthBuffer.byteLength('test')以及new Blob(['test']).size都等于4? - user1063287
Pre-ES6:允许使用UTF-16:请参阅ECMA-262第3版(1999年):第一页说UCS2或UTF-16是可以的。第5页,字符串值的定义:“...虽然每个值通常表示UTF-16文本的单个16位单位,...”。第81页是一个表格,显示匹配代理对如何编码为四个UTF-8字节。 - T S
“per character” - 如果您的意思是“每个用户感知字符”(规范简单解释),它可以是任何数量的16位代码单元。如果您指的是“每个码点”,则可以是UTF-16中的一个或两个16位代码单元。(不能是2.5个代码单元(或者您如何获得5个字节?)) - T S
JavaScript字符串中的每个元素(16位无符号整数值“元素”)是否实际上由两个字节内部表示,在标准中没有定义。(它怎么可能被定义呢 - 只要提供给JavaScript程序的接口遵循标准,一切都按预期工作。)例如,Mozilla可以在字符串仅包含Latin1时每个码点只使用一个字节。 - T S
Unicode代码点转义与字符串长度无关 - 它只是源代码中表示字符串的新方法。('\u{1F600}'.length===2,'\u{1F600}'==='\uD83D\uDE00',`'\u{1F600}'==='') - T S
由于问题不是很清楚,这个答案讨论了字符串在JavaScript引擎中的大小,它以UTF-16格式存储。您的示例将字符串编码为UTF-8并获取UTF-8表示形式的大小,这可能是问题作者想要的。 - T S

8

UTF-8使用1到4个字节来编码字符。正如CMS在接受的答案中指出的那样,JavaScript将使用16位(2个字节)来内部存储每个字符。

如果您通过循环解析字符串中的每个字符并计算每个代码点使用的字节数,然后将总计数乘以2,您应该拥有该UTF-8编码字符串在JavaScript中的内存使用量(以字节为单位)。也许可以像这样:

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

示例:

getStringMemorySize( 'I'    );     //  2
getStringMemorySize( '❤'    );     //  4
getStringMemorySize( ''   );     //  8
getStringMemorySize( 'I❤' );     // 14

4
Lauri Oherd的答案对大多数在实际应用中遇到的字符串都有效,但如果该字符串包含代理对范围内0xD800至0xDFFF之间的单个字符,则会失败。例如:
byteCount(String.fromCharCode(55555))
// URIError: URI malformed

这个较长的函数应该处理所有字符串:

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

例如

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

它将正确计算包含代理对的字符串的大小:
bytes(String.fromCharCode(55555, 57000))
// 4 (not 6)

结果可以与Node的内置函数Buffer.byteLength进行比较:
Buffer.byteLength(String.fromCharCode(55555), 'utf8')
// 3

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接