JavaScriptで文字列を扱うとき、「文字数」と「バイト数」は別物です。
たとえば "ABC" は3文字・3バイトですが、"あいう" は3文字・9バイト(UTF-8)になります。データベースのカラムサイズ制限や API のペイロード上限はバイト単位で設定されることが多く、文字数だけを見ていると上限を超えてエラーになることがあります。
この記事では、TextEncoder・Blob・encodeURIComponent・Node.js Buffer の4つの取得方法から、文字コード別の比較・絵文字のバイト数・バイト数バリデーション・切り詰め関数・パフォーマンス比較まで、実務で必要な知識を網羅的に解説します。
文字数とバイト数の違い
プログラミングで扱う「文字数」と「バイト数」は根本的に異なる概念です。文字数は人間が認識する文字の個数、バイト数はその文字列をコンピュータがメモリ上に格納するときの実際のデータサイズです。
| 文字列 |
文字数 |
UTF-8 バイト数 |
備考 |
"Hello" |
5 |
5 |
ASCII は1文字=1バイト |
"こんにちは" |
5 |
15 |
日本語は1文字=3バイト |
"Hello世界" |
7 |
11 |
混在: 5 + 3×2 |
"??" |
2 |
8 |
絵文字は1文字=4バイト |
注意:JavaScript の string.length は UTF-16 のコードユニット数を返します。これは「文字数」でも「バイト数」でもありません。絵文字(サロゲートペア)では "?".length が 2 になります。
UTF-8 のバイト数ルール
UTF-8 は可変長エンコーディングで、文字によって使うバイト数が異なります。
| バイト数 |
コードポイント範囲 |
対象文字の例 |
| 1バイト |
U+0000 〜 U+007F |
ASCII(A-Z, 0-9, 記号) |
| 2バイト |
U+0080 〜 U+07FF |
ラテン拡張、アラビア文字など |
| 3バイト |
U+0800 〜 U+FFFF |
日本語(ひらがな・カタカナ・漢字) |
| 4バイト |
U+10000 〜 U+10FFFF |
絵文字、CJK拡張漢字 |
方法1: TextEncoder を使う(推奨)
TextEncoder は文字列を UTF-8 の Uint8Array に変換する Web API です。変換後の配列の length がバイト数になります。
JavaScript
function getByteLength(str) {
const encoder = new TextEncoder();
return encoder.encode(str).length;
}
// 使用例
console.log(getByteLength('Hello')); // 5
console.log(getByteLength('こんにちは')); // 15
console.log(getByteLength('?')); // 4
console.log(getByteLength('Hello世界')); // 11
ポイント:TextEncoder は常に UTF-8 でエンコードするため、結果が安定しています。最も推奨される方法です。
TextEncoder のしくみ
TextEncoder.encode() が返す Uint8Array を確認すると、各文字がどのようにバイト列に変換されるかが分かります。
JavaScript
const encoder = new TextEncoder();
// ASCII: 1文字 = 1バイト
console.log(encoder.encode('A')); // Uint8Array [65]
// 日本語: 1文字 = 3バイト
console.log(encoder.encode('あ')); // Uint8Array [227, 129, 130]
// 絵文字: 1文字 = 4バイト
console.log(encoder.encode('?')); // Uint8Array [240, 159, 142, 137]
方法2: Blob を使う方法
Blob オブジェクトはバイナリデータを表現するためのクラスで、size プロパティからバイト数を取得できます。
JavaScript
function getByteLengthBlob(str) {
return new Blob([str]).size;
}
// 使用例
console.log(getByteLengthBlob('Hello')); // 5
console.log(getByteLengthBlob('こんにちは')); // 15
console.log(getByteLengthBlob('?')); // 4
Blob はデフォルトで UTF-8 エンコーディングを使用するため、TextEncoder と同じ結果になります。
注意:Blob はブラウザ環境の API です。Node.js でも v15.7.0 以降で使えますが、TextEncoder の方がパフォーマンスに優れます。
方法3: encodeURIComponent で計算する方法
encodeURIComponent はマルチバイト文字を %XX 形式にエンコードします。この性質を利用してバイト数を計算できます。
JavaScript
function getByteLengthURI(str) {
const encoded = encodeURIComponent(str);
let byteCount = 0;
for (let i = 0; i < encoded.length; i++) {
// %XX は1バイト、それ以外のASCII文字も1バイト
if (encoded[i] === '%') {
byteCount++;
i += 2; // %XX の XX をスキップ
} else {
byteCount++;
}
}
return byteCount;
}
// 使用例
console.log(getByteLengthURI('Hello')); // 5
console.log(getByteLengthURI('こんにちは')); // 15
console.log(getByteLengthURI('?')); // 4
encodeURIComponent の仕組み
なぜこの方法でバイト数が分かるのか、エンコード結果を見てみましょう。
JavaScript
// ASCII文字はそのまま(1バイト)
console.log(encodeURIComponent('A')); // "A" → 1バイト
// 日本語は %XX が3つ(3バイト)
console.log(encodeURIComponent('あ')); // "%E3%81%82" → 3バイト
// 絵文字は %XX が4つ(4バイト)
console.log(encodeURIComponent('?')); // "%F0%9F%8E%89" → 4バイト
ポイント:より簡潔に書く方法もあります。エンコード後の文字列から %XX を1文字に置き換え、その長さを取ればバイト数になります。
JavaScript(簡潔版)
function getByteLengthSimple(str) {
return encodeURIComponent(str).replace(/%[0-9A-F]{2}/gi, 'x').length;
}
console.log(getByteLengthSimple('こんにちは')); // 15
方法4: Buffer.byteLength(Node.js)
Node.js 環境では Buffer.byteLength() メソッドが最も直接的な方法です。エンコーディングを指定してバイト数を取得できます。
Node.js
// デフォルトは UTF-8
console.log(Buffer.byteLength('Hello')); // 5
console.log(Buffer.byteLength('こんにちは')); // 15
console.log(Buffer.byteLength('?')); // 4
// エンコーディングを指定
console.log(Buffer.byteLength('Hello', 'utf8')); // 5
console.log(Buffer.byteLength('Hello', 'utf16le')); // 10
console.log(Buffer.byteLength('Hello', 'ascii')); // 5
console.log(Buffer.byteLength('Hello', 'base64')); // 3 (Base64デコード後)
ポイント:Buffer.byteLength() は文字列を Buffer に変換せずにバイト数だけを計算するため、メモリ効率に優れています。Node.js 環境では最も推奨される方法です。
4つの方法の比較
| 方法 |
環境 |
エンコーディング |
メモリ効率 |
推奨度 |
TextEncoder |
ブラウザ / Node.js |
UTF-8 固定 |
中 |
推奨 |
Blob |
ブラウザ / Node.js 15.7+ |
UTF-8 固定 |
低 |
可 |
encodeURIComponent |
どこでも |
UTF-8 固定 |
低 |
可 |
Buffer.byteLength |
Node.js のみ |
指定可能 |
高 |
推奨(Node.js) |
文字コード別のバイト数比較
同じ文字列でも、使用するエンコーディングによってバイト数が大きく異なります。
| 文字 |
UTF-8 |
UTF-16 |
Shift_JIS |
A(ASCII) |
1バイト |
2バイト |
1バイト |
あ(ひらがな) |
3バイト |
2バイト |
2バイト |
漢(漢字) |
3バイト |
2バイト |
2バイト |
?(絵文字) |
4バイト |
4バイト |
非対応 |
€(ユーロ記号) |
3バイト |
2バイト |
非対応 |
エンコーディングの特徴
- UTF-8: Web標準。ASCII は1バイト、日本語は3バイト。可変長エンコーディング
- UTF-16: JavaScript の内部表現。基本的に2バイト固定、サロゲートペア文字は4バイト
- Shift_JIS: レガシー日本語エンコーディング。ASCII は1バイト、日本語は2バイト。絵文字非対応
Node.js で文字コード別のバイト数を確認する
Node.js
const str = 'こんにちは';
console.log('UTF-8 :', Buffer.byteLength(str, 'utf8')); // 15
console.log('UTF-16 :', Buffer.byteLength(str, 'utf16le')); // 10
console.log('ASCII :', Buffer.byteLength(str, 'ascii')); // 5 (非ASCII文字は切り捨て)
実行結果
UTF-8 : 15
UTF-16 : 10
ASCII : 5
絵文字のバイト数
絵文字は見た目では1文字ですが、内部的には非常に複雑な構造を持っています。
| 絵文字 |
UTF-8 バイト数 |
.length |
種類 |
| ? |
4 |
2 |
基本絵文字 |
| ?? |
8 |
4 |
国旗(Regional Indicator 2つ) |
| ???? |
25 |
11 |
ZWJ シーケンス(結合絵文字) |
| ?? |
8 |
4 |
肌色修飾子付き |
JavaScript
const encoder = new TextEncoder();
const emojis = [
{ char: '?', name: '基本絵文字' },
{ char: '??', name: '国旗' },
{ char: '????', name: '家族' },
{ char: '??', name: '肌色付き' },
];
emojis.forEach(({ char, name }) => {
const bytes = encoder.encode(char).length;
console.log(`${name}: ${char} → ${bytes}バイト (.length=${char.length})`);
});
実行結果
基本絵文字: ? → 4バイト (.length=2)
国旗: ?? → 8バイト (.length=4)
家族: ???? → 25バイト (.length=11)
肌色付き: ?? → 8バイト (.length=4)
注意:ZWJ シーケンス(Zero Width Joiner で結合された絵文字)は、見た目1文字でも内部的には複数のコードポイントが結合されており、バイト数が非常に大きくなります。バイト数制限を設ける場合はこの点に注意してください。
バイト数制限のバリデーション
データベースやAPIでは、カラムサイズやペイロードにバイト数の上限が設けられています。入力値がバイト数制限を超えていないかチェックする関数を作りましょう。
JavaScript
class ByteValidator {
constructor(maxBytes) {
this.maxBytes = maxBytes;
this.encoder = new TextEncoder();
}
/** バイト数を取得 */
getByteLength(str) {
return this.encoder.encode(str).length;
}
/** 制限内かチェック */
isValid(str) {
return this.getByteLength(str) <= this.maxBytes;
}
/** 残りバイト数 */
remaining(str) {
return this.maxBytes - this.getByteLength(str);
}
/** 詳細なバリデーション結果 */
validate(str) {
const byteLength = this.getByteLength(str);
return {
isValid: byteLength <= this.maxBytes,
byteLength,
maxBytes: this.maxBytes,
remaining: this.maxBytes - byteLength,
charLength: str.length,
};
}
}
// 使用例: MySQL VARCHAR(255) のバイト数チェック
const validator = new ByteValidator(255);
console.log(validator.validate('Hello World'));
// { isValid: true, byteLength: 11, maxBytes: 255, remaining: 244, charLength: 11 }
console.log(validator.validate('あ'.repeat(90)));
// { isValid: true, byteLength: 270, maxBytes: 255, remaining: -15, charLength: 90 }
実行結果
{ isValid: true, byteLength: 11, maxBytes: 255, remaining: 244, charLength: 11 }
{ isValid: false, byteLength: 270, maxBytes: 255, remaining: -15, charLength: 90 }
フォーム入力でのリアルタイムバリデーション
HTML + JavaScript
<textarea id="input" maxlength="1000"></textarea>
<p id="counter"></p>
<script>
const MAX_BYTES = 255;
const encoder = new TextEncoder();
const input = document.getElementById('input');
const counter = document.getElementById('counter');
input.addEventListener('input', () => {
const bytes = encoder.encode(input.value).length;
const remaining = MAX_BYTES - bytes;
counter.textContent = `${bytes} / ${MAX_BYTES} バイト(残り ${remaining} バイト)`;
counter.style.color = remaining < 0 ? 'red' : '#333';
});
</script>
指定バイト数で文字列を切り詰める関数
バイト数制限を超えた場合、文字列を安全に切り詰める必要があります。マルチバイト文字の途中で切り詰めると文字化けが発生するため、文字単位で安全に切り詰める関数を実装します。
基本的な切り詰め関数
JavaScript
function truncateByBytes(str, maxBytes) {
const encoder = new TextEncoder();
const encoded = encoder.encode(str);
// バイト数が制限以内ならそのまま返す
if (encoded.length <= maxBytes) {
return str;
}
// maxBytes で切り詰め、デコードして安全な文字列に戻す
const decoder = new TextDecoder('utf-8', { fatal: false });
const truncated = decoder.decode(encoded.slice(0, maxBytes));
// 末尾の不完全な文字(U+FFFD)を除去
return truncated.replace(/\uFFFD$/, '');
}
// 使用例
console.log(truncateByBytes('Hello World', 5)); // "Hello"
console.log(truncateByBytes('こんにちは', 9)); // "こんに"
console.log(truncateByBytes('こんにちは', 10)); // "こんに"(10バイト目は「ち」の途中)
console.log(truncateByBytes('ABC?DEF', 7)); // "ABC?"
省略記号(…)付きの切り詰め
JavaScript
function truncateByBytesWithEllipsis(str, maxBytes, ellipsis = '...') {
const encoder = new TextEncoder();
// 全体がバイト制限以内ならそのまま
if (encoder.encode(str).length <= maxBytes) {
return str;
}
const ellipsisBytes = encoder.encode(ellipsis).length;
const targetBytes = maxBytes - ellipsisBytes;
// 省略記号分を差し引いた文字列を切り詰め
const decoder = new TextDecoder('utf-8', { fatal: false });
const encoded = encoder.encode(str);
let truncated = decoder.decode(encoded.slice(0, targetBytes));
truncated = truncated.replace(/\uFFFD$/, '');
return truncated + ellipsis;
}
// 使用例
console.log(truncateByBytesWithEllipsis('こんにちは世界', 15));
// "こんにち..." (12 + 3 = 15バイト)
パフォーマンス比較
4つの方法を10万回実行してパフォーマンスを比較します。
JavaScript(ベンチマーク)
const str = 'テスト文字列 Test String ?';
const ITERATIONS = 100_000;
// 1. TextEncoder
const encoder = new TextEncoder();
console.time('TextEncoder');
for (let i = 0; i < ITERATIONS; i++) {
encoder.encode(str).length;
}
console.timeEnd('TextEncoder');
// 2. Blob
console.time('Blob');
for (let i = 0; i < ITERATIONS; i++) {
new Blob([str]).size;
}
console.timeEnd('Blob');
// 3. encodeURIComponent
console.time('encodeURIComponent');
for (let i = 0; i < ITERATIONS; i++) {
encodeURIComponent(str).replace(/%[0-9A-F]{2}/gi, 'x').length;
}
console.timeEnd('encodeURIComponent');
| 方法 |
10万回の実行時間(目安) |
相対速度 |
TextEncoder |
約 20〜50ms |
最速 |
Buffer.byteLength(Node.js) |
約 5〜15ms |
最速(Node.js) |
encodeURIComponent |
約 100〜200ms |
中速 |
Blob |
約 200〜500ms |
低速 |
ポイント:TextEncoder はインスタンスを使い回すことでさらにパフォーマンスが向上します。ループ内で new TextEncoder() を毎回呼ぶのは避けましょう。
ブラウザ互換性
各メソッドのブラウザ対応状況を確認しましょう。
| メソッド |
Chrome |
Firefox |
Safari |
Edge |
Node.js |
TextEncoder |
38+ |
18+ |
10.1+ |
79+ |
8.3+ |
Blob |
20+ |
13+ |
6+ |
79+ |
15.7+ |
encodeURIComponent |
1+ |
1+ |
1+ |
12+ |
0.10+ |
Buffer.byteLength |
– |
– |
– |
– |
0.1+ |
環境別の推奨メソッド
- モダンブラウザ:
TextEncoder(最もシンプルで高速)
- レガシーブラウザ対応:
encodeURIComponent(IE含む全ブラウザ対応)
- Node.js:
Buffer.byteLength(最もメモリ効率が高い)
- ユニバーサル:
TextEncoder(ブラウザ・Node.js 両対応)
実務でよく使うパターン
実際の開発でバイト数チェックが必要になる代表的なケースを紹介します。
1. データベースのカラムサイズ制限チェック
MySQL の VARCHAR(255) は、UTF-8 の場合バイト数で 765 バイト(utf8mb3)または 1020 バイト(utf8mb4)が上限になります。
JavaScript
// MySQL VARCHAR のバイト数制限チェック
function validateForMySQL(value, varcharLength, charset = 'utf8mb4') {
const encoder = new TextEncoder();
const bytes = encoder.encode(value).length;
// MySQL の VARCHAR は文字数制限だが、行サイズにバイト制限がある
const maxBytesPerChar = charset === 'utf8mb4' ? 4 : 3;
const maxBytes = varcharLength * maxBytesPerChar;
return {
charLength: [...value].length,
byteLength: bytes,
maxBytes,
isValid: bytes <= maxBytes,
};
}
console.log(validateForMySQL('テストデータ', 255));
// { charLength: 5, byteLength: 15, maxBytes: 1020, isValid: true }
2. API リクエストボディのサイズ制限
多くの API にはリクエストボディのサイズ制限があります(例: AWS API Gateway は 10MB、Firebase は 1MB)。
JavaScript
function checkPayloadSize(data, maxMB = 1) {
const json = JSON.stringify(data);
const encoder = new TextEncoder();
const bytes = encoder.encode(json).length;
const maxBytes = maxMB * 1024 * 1024;
return {
sizeKB: (bytes / 1024).toFixed(2),
sizeMB: (bytes / 1024 / 1024).toFixed(4),
maxMB,
isWithinLimit: bytes <= maxBytes,
};
}
const payload = { name: 'テストユーザー', bio: 'あ'.repeat(10000) };
console.log(checkPayloadSize(payload, 1));
// { sizeKB: "29.36", sizeMB: "0.0287", maxMB: 1, isWithinLimit: true }
3. URL の長さ制限チェック
ブラウザやサーバーには URL の長さ制限があります(多くのブラウザは約 2,048 文字、IE は 2,083 文字)。クエリパラメータに日本語を含む場合、エンコード後のバイト数が重要です。
JavaScript
function checkURLLength(baseUrl, params) {
const url = new URL(baseUrl);
Object.entries(params).forEach(([key, val]) => {
url.searchParams.set(key, val);
});
const fullUrl = url.toString();
const MAX_URL_LENGTH = 2048;
return {
length: fullUrl.length,
isValid: fullUrl.length <= MAX_URL_LENGTH,
url: fullUrl,
};
}
const result = checkURLLength(
'https://example.com/search',
{ q: 'JavaScript バイト数 取得方法' }
);
console.log(result.length, result.isValid);
// 99 true
4. localStorage / sessionStorage の容量チェック
JavaScript
function getStorageUsage() {
let totalBytes = 0;
const encoder = new TextEncoder();
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const value = localStorage.getItem(key);
totalBytes += encoder.encode(key).length;
totalBytes += encoder.encode(value).length;
}
const MAX_BYTES = 5 * 1024 * 1024; // 約5MB
return {
usedKB: (totalBytes / 1024).toFixed(2),
usedPercent: ((totalBytes / MAX_BYTES) * 100).toFixed(2) + '%',
remainingKB: ((MAX_BYTES - totalBytes) / 1024).toFixed(2),
};
}
console.log(getStorageUsage());
// { usedKB: "12.50", usedPercent: "0.24%", remainingKB: "5107.50" }
ユーティリティ関数まとめ
ここまで紹介した関数をまとめた、コピー&ペーストで使えるユーティリティモジュールです。
byteUtils.js
const encoder = new TextEncoder();
const decoder = new TextDecoder('utf-8', { fatal: false });
/** UTF-8 バイト数を取得 */
export function getByteLength(str) {
return encoder.encode(str).length;
}
/** バイト数が制限以内か判定 */
export function isWithinByteLimit(str, maxBytes) {
return getByteLength(str) <= maxBytes;
}
/** 指定バイト数で安全に切り詰め */
export function truncateByBytes(str, maxBytes, ellipsis = '') {
const encoded = encoder.encode(str);
if (encoded.length <= maxBytes) return str;
const ellipsisBytes = encoder.encode(ellipsis).length;
const target = maxBytes - ellipsisBytes;
let result = decoder.decode(encoded.slice(0, target));
result = result.replace(/\uFFFD$/, '');
return result + ellipsis;
}
/** バリデーション結果を返す */
export function validateByteLength(str, maxBytes) {
const byteLength = getByteLength(str);
return {
isValid: byteLength <= maxBytes,
byteLength,
maxBytes,
remaining: maxBytes - byteLength,
};
}
使用例
import { getByteLength, truncateByBytes, validateByteLength } from './byteUtils.js';
console.log(getByteLength('Hello世界?')); // 15
console.log(truncateByBytes('こんにちは世界', 15, '...'));
// "こんにち..."
console.log(validateByteLength('テスト', 255));
// { isValid: true, byteLength: 9, maxBytes: 255, remaining: 246 }
まとめ
JavaScript で文字列のバイト数を取得する方法を4つ紹介しました。それぞれの特徴を整理します。
| 方法 |
特徴 |
用途 |
TextEncoder |
シンプル・高速・ブラウザ / Node.js 両対応 |
一般的な用途すべて |
Blob |
ワンライナーで書ける |
簡易的な確認 |
encodeURIComponent |
レガシーブラウザ対応 |
IE 対応が必要な場合 |
Buffer.byteLength |
最高速・エンコーディング指定可 |
Node.js サーバーサイド |
記事のポイント
- 文字数とバイト数は別物。日本語は1文字=3バイト(UTF-8)、絵文字は4バイト以上
- ブラウザでは
TextEncoder が最も推奨される方法
- Node.js では
Buffer.byteLength が最もメモリ効率がよい
- バイト数制限がある場合、切り詰め関数で文字化けを防ぐ
- DB のカラムサイズや API の制限はバイト単位で管理されることが多い
- 絵文字(特に ZWJ シーケンス)は見た目1文字でも大量のバイトを消費する
関連記事