【JavaScript】文字列のバイト数を取得する方法(完全ガイド)

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 のコードユニット数を返します。これは「文字数」でも「バイト数」でもありません。絵文字(サロゲートペア)では "?".length2 になります。

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

実行結果

5
15
4
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

実行結果

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

実行結果

5
15
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デコード後)

実行結果

5
15
4
5
10
5
3

ポイント: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?"

実行結果

Hello
こんに
こんに
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文字でも大量のバイトを消費する

関連記事