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

在我的 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个字节。

164678 次浏览

在 JavaScript 中没有原生的方法可以做到这一点(请参阅 里卡多 · 加利的回答了解现代的方法)


用于历史参考或 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);
}

实际上,我发现了问题所在。要让代码在 <head>页面上工作,应该有这样一个标签:

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

或者,正如注释中建议的那样,如果服务器发送 HTTPContent-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只是个巧合。

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

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

来源

你可以试试这个:

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

对我有用。

下面是一个更快的版本,它不使用正则表达式,也不使用 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 代码点(基于 Wikipedia 对 UTF8和 UTF16代理字符的描述)的 UTF8长度。

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

几年过去了,现在你可以在本地做了

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

请注意,IE 不支持它(您可以为此使用 使用填充物)。

MDN 文档

标准规格

下面是一个计算字符串 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格式不正确的,则该方法可能抛出错误

这将工作的 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.

对于简单的 UTF-8编码,Blob 的兼容性略好于 TextEncoder。但是在非常老的浏览器中不起作用。

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

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

Buffer.byteLength(string, 'utf8')


Buffer.from(string).length

我花了一段时间来找到 本土反应的解决方案,所以我把它放在这里:

首先安装 buffer软件包:

npm install --save buffer

然后使用 node 方法:

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

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

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

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

为了提高速度,我比较了 Firefox 中提供的一些方法。

我使用的字符串包含以下字符: ± 0.000000000000000000000000000000000000000000000000000000000000000000000000

所有的结果都是平均每个运行3次。时间是毫秒。注意,所有 URIEncoding 方法的行为都相似,并且有极端的结果,所以我只包含了一个。

虽然有一些基于字符串大小的波动,charCode 方法(lovasoa 和 fuweichin)都执行相似和最快的整体,而 fuweichin 的 charCode 方法最快。Blob 和 TextEncoder 方法的执行方式相似。一般来说,charCode 方法比 Blob 和 TextEncoder 方法快约75% 。URIEncoding 方法基本上是不可接受的。

以下是我得到的结果:

大小6.4 * 10 ^ 6字节:

Lauri Oherd – URIEncoding:     6400000    et: 796
lovasoa – charCode:            6400000    et: 15
fuweichin – charCode2:         6400000    et: 16
simap – Blob:                  6400000    et: 26
Riccardo Galli – TextEncoder:  6400000    et: 23

大小19.2 * 10 ^ 6字节: 大块头在这里做一些奇怪的事情。

Lauri Oherd – URIEncoding:     19200000    et: 2322
lovasoa – charCode:            19200000    et: 42
fuweichin – charCode2:         19200000    et: 45
simap – Blob:                  19200000    et: 169
Riccardo Galli – TextEncoder:  19200000    et: 70

大小64 * 10 ^ 6字节:

Lauri Oherd – URIEncoding:     64000000    et: 12565
lovasoa – charCode:            64000000    et: 138
fuweichin – charCode2:         64000000    et: 133
simap – Blob:                  64000000    et: 231
Riccardo Galli – TextEncoder:  64000000    et: 211

大小192 * 10 ^ 6字节: URIEncoding 方法此时会冻结浏览器。

lovasoa – charCode:            192000000    et: 754
fuweichin – charCode2:         192000000    et: 480
simap – Blob:                  192000000    et: 701
Riccardo Galli – TextEncoder:  192000000    et: 654

大小640 * 10 ^ 6字节:

lovasoa – charCode:            640000000    et: 2417
fuweichin – charCode2:         640000000    et: 1602
simap – Blob:                  640000000    et: 2492
Riccardo Galli – TextEncoder:  640000000    et: 2338

大小1280 * 10 ^ 6字节: Blob & TextEncoder 方法开始碰壁了。

lovasoa – charCode:            1280000000    et: 4780
fuweichin – charCode2:         1280000000    et: 3177
simap – Blob:                  1280000000    et: 6588
Riccardo Galli – TextEncoder:  1280000000    et: 5074

大小1920 * 10 ^ 6字节:

lovasoa – charCode:            1920000000    et: 7465
fuweichin – charCode2:         1920000000    et: 4968
JavaScript error: file:///Users/xxx/Desktop/test.html, line 74: NS_ERROR_OUT_OF_MEMORY:

密码如下:

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


function byteLengthCharCode(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;
}


function byteLengthCharCode2(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;
}


function byteLengthBlob(str) {
return new Blob([str]).size;
}


function byteLengthTE(str) {
return (new TextEncoder().encode(str)).length;
}


var sample = "😀œ´®†¥¨ˆøπ¬˚∆˙©ƒ∂ßåΩ≈ç√∫˜µ≤i";
var string = "";


// Adjust multiplier to change length of string.
let mult = 1000000;


for (var i = 0; i < mult; i++) {
string += sample;
}


let t0;


try {
t0 = Date.now();
console.log("Lauri Oherd – URIEncoding:   " + byteLengthURIEncoding(string) + "    et: " + (Date.now() - t0));
} catch(e) {}


t0 = Date.now();
console.log("lovasoa – charCode:            " + byteLengthCharCode(string) + "    et: " + (Date.now() - t0));


t0 = Date.now();
console.log("fuweichin – charCode2:         " + byteLengthCharCode2(string) + "    et: " + (Date.now() - t0));


t0 = Date.now();
console.log("simap – Blob:                  " + byteLengthBlob(string) + "    et: " + (Date.now() - t0));


t0 = Date.now();
console.log("Riccardo Galli – TextEncoder:  " + byteLengthTE(string) + "    et: " + (Date.now() - t0));

碰到了这个 如何使用包含 HTML 的字符串在 javascript 中模拟 mb _ strlen

绳子和之前的答案不匹配。

我得到了8的预期长度:

const str = 'X&nbsp;&#34;FUEL&#34;'
const div = document.createElement("div");
div.innerHTML = str
console.log(div.textContent.length)

我正在检查长度:

const str = "🌧as%20😈"
const len = new URL(str.replace(/%[A-F0-9]{2}/g, "..."), "https:$").pathname.replace(/%[A-F0-9]{2}/g, "-").length - 1
console.log(len) // 13

当 i (尝试)验证目录名是否小于180个字符时

sizeInBytes = Buffer.from(data).length

例如:

let data = 'šč'; // data with utf-8 characters
console.log( data.length ); // 2
console.log( Buffer.from(data).length ); // 4

基于 下列基准,这似乎是所有平台上最快的选择:

我已经创建了实现以上内容的 以下图书馆:

import stringByteLength from 'string-byte-length'


stringByteLength('test') // 4
stringByteLength(' ') // 1
stringByteLength('\0') // 1
stringByteLength('±') // 2
stringByteLength('★') // 3
stringByteLength('🦄') // 4