Длина строки в байтах в JavaScript
В моем JavaScript-коде мне нужно написать сообщение серверу в таком формате:
<size in bytes>CRLF
<data>CRLF
Пример:
3
foo
Данные могут содержать символы Юникода. Мне нужно отправить их как UTF-8.
Я ищу самый кросс-браузерный способ вычисления длины строки в байтах в JavaScript.
Я пробовал это, чтобы составить полезную нагрузку:
return unescape(encodeURIComponent(str)).length + "\n" + str + "\n"
Но это не дает мне точных результатов для старых браузеров (или, может быть, строк в этих браузерах в UTF-16?).
Любые подсказки?
Update:
Пример: длина в байтах строки ЭЭХ! Naïve?
в UTF-8 составляет 15 байт, но некоторые браузеры указывают вместо этого 23 байта.
Ответы
Ответ 1
Нет никакого способа сделать это в JavaScript изначально. (См. Риккардо Галли ответ для современного подхода.)
Для исторической справки или в тех случаях, когда API-интерфейсы TextEncoder все еще недоступны.
Если вы знаете кодировку символов, вы можете вычислить ее самостоятельно.
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);
}
Ответ 2
Прошли годы, и в настоящее время вы можете сделать это изначально
(new TextEncoder().encode('foo')).length
Обратите внимание, что он пока не поддерживается IE (или Edge) (для этого вы можете использовать полифилл).
Документация MDN
Стандартные спецификации
Ответ 3
Вот гораздо более быстрая версия, в которой не используются ни регулярные выражения, ни 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;
}
Вот сравнение производительности.
Он просто вычисляет длину в UTF8 каждой кодовой точки Unicode, возвращаемой charCodeAt() (на основе описаний UTF8 в Википедии и суррогатных символов UTF16).
Это следует RFC3629 (где символы UTF-8 имеют длину не более 4 байтов).
Ответ 4
Для простой кодировки UTF-8, с немного лучшей совместимостью, чем TextEncoder
, Blob делает трюк. Однако не работает в очень старых браузерах.
new Blob(["😀"]).size; // -> 4
Ответ 5
Эта функция вернет размер байта любой строки UTF-8, которую вы передадите ей.
function byteCount(s) {
return encodeURI(s).split(/%..|./).length - 1;
}
Источник
Ответ 6
Другой очень простой подход с использованием Buffer
(только для NodeJS):
Buffer.from(string).length
Ответ 7
Собственно, я понял, что случилось. Для работы кода страница <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.
Ответ 8
Вот независимый и эффективный метод подсчета байтов 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));
Ответ 9
Мне понадобилось время, чтобы найти решение для React Native, поэтому я выложу его здесь:
Сначала установите buffer
пакет:
npm install --save buffer
Затем используйте метод узла:
const { Buffer } = require('buffer');
const length = Buffer.byteLength(string, 'utf-8');
Ответ 10
Это будет работать для символов 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.
Ответ 11
В NodeJS Buffer.byteLength
- это метод специально для этой цели:
let strLengthInBytes = Buffer.byteLength(str); // str is UTF-8
Обратите внимание, что по умолчанию метод предполагает, что строка находится в кодировке UTF-8. Если требуется другая кодировка, передайте ее в качестве второго аргумента.
Ответ 12
Вы можете попробовать следующее:
function getLengthInBytes(str) {
var b = str.match(/[^\x00-\xff]/g);
return (str.length + (!b ? 0: b.length));
}
Это работает для меня.