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.