JavaScript中的字符串长度(以字节为单位)


104

在我的JavaScript代码中,我需要以以下格式编写一条消息到服务器:

<size in bytes>CRLF
<data>CRLF

例:

3
foo

数据可能包含unicode字符。我需要将它们作为UTF-8发送。

我正在寻找最跨浏览器的方法来计算JavaScript中字符串的长度(以字节为单位)。

我已经尝试过以此来组成我的有效载荷:

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

但是对于较旧的浏览器(或UTF-16中的那些浏览器中的字符串?),它不能为我提供准确的结果。

有什么线索吗?

更新:

示例:ЭЭХ! Naïve?UTF-8中字符串的字节长度为15个字节,但是某些浏览器却报告为23个字节。



@Eli:您所链接的问题中没有一个对我有用。
Alexander Gladysh 2011年

当您谈论“ЭЭХ!天真?”时 你有没有把它变成一种特殊的形式? unicode.org/reports/tr15
Mike Samuel

@Mike:我在随机文本编辑器中输入了它(在UTF-8模式下)并保存了它。就像我图书馆的任何用户一样。但是,似乎我发现了问题所在-请参阅我的答案。
Alexander Gladysh 2011年

Answers:


89

无法在JavaScript中本地执行此操作。(有关现代方法,请参阅Riccardo Galli的答案。)


供历史参考或TextEncoder API 仍然不可用的地方

如果您知道字符编码,则可以自己计算。

encodeURIComponent 假定UTF-8为字符编码,因此,如果需要该编码,可以这样做,

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

由于UTF-8编码多字节序列的方式,因此应该可以使用。对于单个字节序列,第一个编码字节始终以零的高位开始,或者第一个十六进制数字为C,D,E或F的字节开始。第二个和后续字节是前两个位为10的字节这些是要在UTF-8中计数的额外字节。

维基百科中的表格更加清晰

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

相反,如果您需要了解页面编码,则可以使用以下技巧:

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

好吧,我怎么知道数据的字符编码?我需要对提供给我的JS库的任何字符串用户(程序员)进行编码。
Alexander Gladysh 2011年

@Alexander,将消息发送到服务器时,是否通过HTTP标头指定消息正文的内容编码?
Mike Samuel

1
@亚历山大,很酷。如果要建立协议,则强制UTF-8是进行文本交换的好方法。一个较小的变量可能导致不匹配。UTF-8应该是字符编码的网络字节顺序。
Mike Samuel

4
@MikeSamuel:lengthInUtf8Bytes对于非BMP字符,该函数返回5,与str.length这些返回值2相同。我将对该函数的修改版本进行解答。
Lauri Oherd

1
这个解决方案很酷,但是不考虑utf8mb4。例如,encodeURIComponent('🍀')'%F0%9F%8D%80'
艾伯特

117

多年过去了,如今您可以在本地进行操作

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

请注意,IE(或Edge)尚不支持它(您可以为此使用polyfill)。

MDN文档

标准规格


4
多么奇妙的现代方法。谢谢!
Con Antonakos '16

请注意,根据MDN文档,Safari(WebKit)尚不支持TextEncoder。
Maor

TextEncode仅支持UTF-8 ,因为铬53.
Jehong安

1
如果只需要长度,则分配一个新的字符串,进行实际的转换,获取该长度,然后丢弃该字符串可能是过大的选择。请参阅上面的答案,以了解一个仅以有效方式计算长度的函数。
lovasoa

66

这是一个更快的版本,它不使用正则表达式,也不使用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;
}

这是性能比较

它只是计算charCodeAt()返回的每个Unicode代码点的UTF8长度(基于Wikipedia对UTF8和UTF16替代字符的描述)。

它遵循RFC3629(其中UTF-8字符的长度最多为4个字节)。


46

对于简单的UTF-8编码,其兼容性要比稍好TextEncoder,Blob可以解决问题。但是,在旧版浏览器中将无法使用。

new Blob(["😀"]).size; // -> 4  

29

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

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

资源


它不适用于字符串'ユーザーコード',预期长度为14,但21
May Weather VN

1
@MayWeatherVN,您错误ユーザーコード的字节长度始终为21,我在differents工具上对其进行了测试;对您的评论更友好;)
Capitex

我记得在php上测试过的字符串是14
May Weather VN

24

另一种非常简单的使用方法Buffer(仅适用于NodeJS):

Buffer.byteLength(string, 'utf8')

Buffer.from(string).length

1
您可以使用跳过创建缓冲区Buffer.byteLength(string, 'utf8')

1
@Joe感谢您的建议,我刚刚对其进行了编辑以包含其中。
伊万·佩雷斯

5

花了我一段时间找到React Native的解决方案,所以我将其放在这里:

首先安装buffer软件包:

npm install --save buffer

然后使用node方法:

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

4

实际上,我发现了问题所在。为了使代码正常工作,页面<head>应具有以下标记:

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

或者,如注释中所建议,如果服务器发送HTTP Content-Encoding标头,则它也应正常工作。

然后来自不同浏览器的结果是一致的。

这是一个例子:

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

注意:我怀疑指定任何(准确的)编码都会解决编码问题。我需要UTF-8只是一个巧合。


2
unescapeJavaScript函数不应当被用来解码统一资源标识符(URI)。
Lauri Oherd

1
unescape实际上,绝对不应使用@LauriOherd 解码URI。但是,要将文本转换为UTF-8,效果很好
TS

unescape(encodeURIComponent(...)).length始终计算正确的长度(带或不带)meta http-equiv ... utf8。如果没有编码规范,则某些浏览器可能只是具有不同的文本(在将文档的字节编码为实际的html文本之后),它们的计算长度不同。通过打印长度和文本本身,可以轻松地进行测试。
TS

3

这是一种独立有效的方法来计算字符串的UTF-8字节。

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

请注意,如果输入字符串的UCS-2格式错误,则该方法可能会引发错误。


3

在NodeJS中,Buffer.byteLength是专门用于此目的的方法:

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

请注意,默认情况下,该方法假定字符串采用UTF-8编码。如果需要其他编码,请将其作为第二个参数传递。


是否可以strLengthInBytes仅通过了解字符串中字符的“计数” 来进行计算?即var text = "Hello World!; var text_length = text.length; // pass text_length as argument to some method?。并且,仅供参考,Buffer我刚刚遇到了这个答案该答案new Blob(['test string']).size在节点中讨论 Buffer.from('test string').length。也许这些也会对某些人有帮助?
user1063287

1
@ user1063287问题是字符数并不总是等于字节数。例如,常见的UTF-8编码是可变宽度编码,其中单个字符的大小可以是1字节到4字节。这就是为什么需要一种特殊的方法以及所使用的编码的原因。
波阿斯

例如,如果每个字符只有1个字节,则包含4个字符的UTF-8字符串可能至少为4个字节“长”;如果每个字符为4个字节,则最多为16个字节“长”。请注意,无论哪种情况,字符数仍为4,因此这不是字节长度的可靠度量。
波阿斯

1

这将适用于BMP和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. 

0

您可以尝试以下方法:

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

这个对我有用。


chrome中的“â”返回1
Rick

第一个问题可以通过改变\ XFF到\ 0x7F部分是固定的,但不能修复0x800-0xFFFF码点之间将被报告为服用2个字节,当他们采取3的事实
里克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.