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バイトを報告します。


1
重複の可能性はありますか?stackoverflow.com/questions/2219526/...
イーライ

@Eli:あなたが私のために働くようにリンクした質問の答えはどれもありません。
Alexander Gladysh、2011

「ЭЭХ!ナイーブ?」について話すとき あなたはそれを特定の通常の形にしていますか? unicode.org/reports/tr15
マイクサミュエル

@マイク:ランダムテキストエディター(UTF-8モード)に入力して保存しました。私のライブラリのユーザーが行うのと同じように。しかし、何が問題だったかがわかったようです。私の答えをご覧ください。
Alexander Gladysh

回答:


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がマルチバイトシーケンスをエンコードする方法のため、これは機能するはずです。最初のエンコードされたバイトは常に、単一バイトシーケンスのゼロの上位ビット、または最初の16進数がC、D、E、またはFのバイトで始まります。2番目以降のバイトは、最初の2ビットが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

@Alexander、メッセージをサーバーに送信するときに、HTTPヘッダーを介してメッセージ本文のコンテンツエンコーディングを指定していますか?
マイクサミュエル

1
@アレクサンダー、かっこいい。プロトコルを確立している場合は、UTF-8を強制することは、テキスト交換のための素晴らしいアイデアです。不一致になる可能性のある変数が1つ少なくなります。UTF-8は、文字エンコーディングのネットワークバイトオーダーである必要があります。
マイクサミュエル

4
@MikeSamuel:lengthInUtf8Bytes非BMP文字の場合、関数は5をstr.length返します。これらは2を返します。この関数の修正バージョンを回答セクションに書きます。
Lauri Oherd、2012

1
このソリューションはクールですが、utf8mb4は考慮されていません。たとえばencodeURIComponent('🍀')です'%F0%9F%8D%80'
albert

117

何年か経ちましたが、最近ではネイティブで行うことができます

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

IE(またはEdge)ではまだサポートされていません(そのためにポリフィル使用できます)。

MDNのドキュメント

標準仕様


4
なんて素晴らしい、現代的なアプローチでしょう。ありがとう!
Con Antonakos 2016年

MDNのドキュメントによると、TextEncoderはSafari(WebKit)でまだサポートされていないことに注意してください。
Maor 2017年

TextEncodeのみをサポートし、UTF-8クローム53以来
Jehong安

1
長さだけが必要な場合は、新しい文字列を割り当て、実際の変換を行い、長さを取得して、文字列を破棄するのはやり過ぎかもしれません。効率的に長さを計算するだけの関数については、上記の私の答えを参照してください。
lovasoa

66

以下は、正規表現もエンコードURIComponent()も使用しない、はるかに高速なバージョンです。

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()によって返された各ユニコードコードポイントの長さを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です、私はそれを別のツールでテストしました。コメントでもっと親切にしてください;)
Capitex

私がphpでテストしたことを覚えているこの文字列は14
May Weather VN

24

使用する別の非常に単純なアプローチBuffer(NodeJSのみ):

Buffer.byteLength(string, 'utf8')

Buffer.from(string).length

1
でバッファの作成をスキップできBuffer.byteLength(string, 'utf8')ます。
Joe

1
@ジョー提案をありがとう、私はそれを含めるように編集しました。
イバン・ペレス

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関数はないはずのUniform Resource識別子(URI)をデコードするために使用されます。
Lauri Oherd 2012

1
@LauriOherd unescapeは、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エンコーディングであると想定していることに注意してください。別のエンコーディングが必要な場合は、2番目の引数として渡します。


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文字のサイズが1バイトから4バイトになる場合があります。そのため、使用するエンコーディングだけでなく、特別なメソッドも必要です。
Boaz

たとえば、各文字が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)); 
}

わたしにはできる。


クロムで「â」の場合は1を返します
Rick

最初の問題は、x7f \に\ XFFを変更することで固定することができ、それは、彼らは3を取るとき0x800-0xFFFF間のコードポイントは、2つのバイトを取るとして報告されるという事実が解決しない
リック・
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.