JavaScript'te bayt cinsinden dize uzunluğu


104

JavaScript kodumda, sunucuya şu biçimde bir mesaj oluşturmam gerekiyor:

<size in bytes>CRLF
<data>CRLF

Misal:

3
foo

Veriler, unicode karakterler içerebilir. Bunları UTF-8 olarak göndermem gerekiyor.

JavaScript'te dizenin uzunluğunu bayt cinsinden hesaplamanın en çapraz tarayıcı yolunu arıyorum.

Yükümü oluşturmak için bunu denedim:

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

Ama bana eski tarayıcılar (veya belki UTF-16’daki bu tarayıcılardaki dizeler) için doğru sonuçlar vermiyor.

Herhangi bir ipucu?

Güncelleme:

Örnek: ЭЭХ! Naïve?UTF-8'deki dizenin bayt cinsinden uzunluğu 15 bayttır, ancak bazı tarayıcılar bunun yerine 23 bayt bildirir.



@Eli: Bağlandığınız sorudaki yanıtların hiçbiri benim için işe yaramıyor.
— Alexander Gladysh

"ЭЭХ! Naif?" Den bahsederken onu belirli bir normal forma koydunuz mu? unicode.org/reports/tr15
— Mike Samuel

@Mike: Rastgele metin düzenleyicide (UTF-8 modunda) yazdım ve kaydettim. Tıpkı kütüphanemdeki herhangi bir kullanıcının yapacağı gibi. Ancak, neyin yanlış olduğunu anladım - cevabıma bakın.
— Alexander Gladysh

Yanıtlar:


89

Bunu yerel olarak JavaScript'te yapmanın bir yolu yoktur. ( Riccardo Galli'nin modern bir yaklaşım için cevabına bakın .)


Tarihsel referans için veya TextEncoder API'lerinin hala kullanılamadığı durumlarda .

Karakter kodlamasını biliyorsanız, bunu kendiniz de hesaplayabilirsiniz.

encodeURIComponent karakter kodlaması olarak UTF-8'i varsayar, bu nedenle bu kodlamaya ihtiyacınız varsa,

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

Bu, UTF-8'in çok baytlı dizileri kodlama biçimi nedeniyle çalışmalıdır. İlk kodlanmış bayt her zaman ya tek bir bayt dizisi için yüksek bir sıfır bitiyle ya da ilk onaltılık basamağı C, D, E veya F olan bir baytla başlar. İkinci ve sonraki baytlar, ilk iki biti 10 olan baytlardır. Bunlar UTF-8'de saymak istediğiniz fazladan baytlardır.

Wikipedia'daki tablo daha net hale getiriyor

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

Bunun yerine sayfa kodlamasını anlamanız gerekiyorsa, şu numarayı kullanabilirsiniz:

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

Peki, verilerin karakter kodlamasını nasıl bilebilirim? JS kitaplığıma sağlanan kullanıcı (programcı) dizesini kodlamam gerekiyor.
— Alexander Gladysh

@Alexander, mesajı sunucuya gönderirken, mesaj gövdesinin içerik kodlamasını bir HTTP başlığı aracılığıyla mı belirtiyorsunuz?
— Mike Samuel

1
@Alexander, harika. Bir protokol oluşturuyorsanız, UTF-8'i zorunlu kılmak, metin değişimi için harika bir fikirdir. Bir uyuşmazlığa neden olabilecek daha az değişken. UTF-8, karakter kodlamalarının ağ bayt sıralaması olmalıdır.
— Mike Samuel

4
@MikeSamuel: lengthInUtf8Bytesİşlev str.length, bu dönüşler 2 için olduğu gibi BMP olmayan karakterler için 5 döndürür. Bu işlevin değiştirilmiş bir sürümünü yanıtlar bölümüne yazacağım.
— Lauri Oherd

1
Bu çözüm harika ama utf8mb4 dikkate alınmıyor. Örneğin, encodeURIComponent('🍀')bir '%F0%9F%8D%80'.
— albert

117

Yıllar geçti ve bugünlerde bunu yerel olarak yapabilirsiniz

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

Henüz IE (veya Edge) tarafından desteklenmediğini unutmayın (bunun için bir çoklu dolgu kullanabilirsiniz ).

MDN belgeleri

Standart özellikler


4
Ne harika, modern bir yaklaşım. Teşekkürler!
— Con Antonakos

MDN belgelerine göre TextEncoder'ın henüz Safari (WebKit) tarafından desteklenmediğine dikkat edin .
— Maor

TextEncodeChrome
— 53'ten

1
Yalnızca uzunluğa ihtiyacınız varsa, yeni bir dizi ayırmak, gerçek dönüşümü yapmak, uzunluğu almak ve ardından dizeyi atmak aşırı olabilir. Uzunluğu verimli bir şekilde hesaplayan bir işlev için yukarıdaki cevabıma bakın.
— lovasoa

66

İşte normal ifadeler veya encodeURIComponent () kullanmayan çok daha hızlı bir sürüm :

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

İşte bir performans karşılaştırması .

CharCodeAt () tarafından döndürülen her bir unicode kod noktasının UTF8 cinsinden uzunluğunu hesaplar (wikipedia'nın UTF8 ve UTF16 yedek karakterlerinin açıklamalarına göre ).

RFC3629'u izler (burada UTF-8 karakterleri en fazla 4 bayt uzunluğundadır).


46

Basit UTF-8 kodlaması için, bundan biraz daha iyi uyumlulukla TextEncoder, Blob hile yapar. Yine de çok eski tarayıcılarda çalışmaz.

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

29

Bu işlev, kendisine ilettiğiniz herhangi bir UTF-8 dizesinin bayt boyutunu döndürür.

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

Kaynak


'ユ ー ザ ー コ ー ド' dizesiyle çalışmıyor, 14 uzunluk bekleniyor, ancak 21
— Mayıs

1
@ MayWeatherVN ユーザーコード, bayt cinsinden yanlış uzunluk her zaman 21'dir, farklı araçlarda test ettim; yorumlarınızla daha nazik olun;)
— Capitex

Php üzerinde test ettiğimi hatırladığım bu dize 14
— Mayıs Hava Durumu VN

23

Kullanan başka bir çok basit yaklaşım Buffer(yalnızca NodeJS için):

Buffer.byteLength(string, 'utf8')

Buffer.from(string).length

1
İle tampon oluşturmayı atlayabilirsiniz Buffer.byteLength(string, 'utf8').
— Joe

1
@Joe Öneriniz için teşekkürler, onu eklemek için bir düzenleme yaptım.
— Iván Pérez

5

React Native için bir çözüm bulmam biraz zaman aldı, bu yüzden buraya koyacağım:

Önce bufferpaketi kurun :

npm install --save buffer

Ardından düğüm yöntemini kullanın:

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

4

Aslında sorunun ne olduğunu anladım. Kodun çalışması için sayfanın <head>şu etiketi olması gerekir:

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

Veya yorumlarda önerildiği gibi, sunucu HTTP Content-Encodingüstbilgisi gönderirse , onun da çalışması gerekir.

Daha sonra farklı tarayıcılardan alınan sonuçlar tutarlıdır.

İşte bir örnek:

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

Not: Herhangi bir (doğru) kodlamanın belirtilmesinin kodlama sorununu çözeceğinden şüpheleniyorum . UTF-8'e ihtiyacım olması sadece bir tesadüf.


2
unescapeJavaScript işlevi olmamalıdır Tekdüzen Kaynak Tanımlayıcılarını (URI) çözmek için kullanılabilir.
— Lauri Oherd

1
@LauriOherd unescape, URI'lerin kodunu çözmek için asla kullanılmamalıdır. Bununla birlikte, metni UTF-8'e dönüştürmek iyi
— TS

unescape(encodeURIComponent(...)).lengthher zaman ile veya olmadan doğru uzunluğu hesaplar meta http-equiv ... utf8. Bir kodlama belirtimi olmadan, bazı tarayıcılar , uzunluğunu hesapladıkları farklı bir metne sahip olabilir (belgenin baytlarını gerçek html metnine kodladıktan sonra). Sadece uzunluğu değil, metnin kendisini de yazdırarak bunu kolayca test edebilirsiniz.
— TS

3

İşte bir dizenin UTF-8 baytlarını saymak için bağımsız ve verimli bir yöntem.

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

Bir girdi dizesi UCS-2 hatalı biçimlendirilmişse yöntemin hata verebileceğini unutmayın .


3

NodeJS'de, Buffer.byteLengthözellikle bu amaca yönelik bir yöntemdir:

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

Yöntemin varsayılan olarak dizenin UTF-8 kodlamasında olduğunu varsaydığını unutmayın. Farklı bir kodlama gerekiyorsa, ikinci argüman olarak iletin.


strLengthInBytesSadece dizedeki karakterlerin 'sayısını' bilerek hesaplamak mümkün mü ? yani var text = "Hello World!; var text_length = text.length; // pass text_length as argument to some method?. Ve sadece referans için, yeniden Buffer- tartışan bu yanıta rastladım new Blob(['test string']).sizeve düğümde Buffer.from('test string').length,. Belki bunlar da bazı insanlara yardımcı olur?
— user1063287

1
@ user1063287 Sorun, karakter sayısının her zaman bayt sayısına eşit olmamasıdır. Örneğin, yaygın UTF-8 kodlaması, tek bir karakterin boyut olarak 1 bayt ila 4 bayt olabileceği değişken genişlikli bir kodlamadır. Bu yüzden kullanılan kodlamanın yanı sıra özel bir yönteme ihtiyaç vardır.
— Boaz

Örneğin, 4 karakterli bir UTF-8 dizesi, her karakter yalnızca 1 bayt ise en az 4 bayt "uzun" olabilir; ve her karakter 4 bayt ise en fazla 16 bayt "uzun". Her iki durumda da karakter sayısının hala 4 olduğunu ve bu nedenle bayt uzunluğu için güvenilmez bir ölçü olduğunu unutmayın .
— Boaz

1

Bu, BMP ve SIP / SMP karakterleri için işe yarar.

    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

Bunu deneyebilirsiniz:

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

Benim için çalışıyor.


chrome'da "â" için 1 değerini döndürür
— Rick

ilk sorun \ xff \ x7f olarak değiştirilerek düzeltilebilir, ancak bu 0x800-0xFFFF arasındaki kod noktalarının 3 aldıklarında 2 bayt olarak raporlanacağı gerçeğini düzeltmez.
— Rick
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.