文字列の長さを取得するには
文字列の長さを数える基本形を各言語で示す。
len や length が返すのが文字数ではなく、バイト数・UTF-16 の単位数・コードポイントの数のどれかであるという言語ごとの違いと、絵文字や国旗で値が食い違う点までを扱う。
公開:
各言語見出しの横のバッジは検証状態を表す。実行確認済みはコードを実際に実行して確認したもの、静的確認は構文と公式 API ドキュメントで確認したものである。
Python 実行確認済み
s = "こんにちは"
print(len(s)) # 5(コードポイントの数)
print(len(s.encode("utf-8"))) # 15(UTF-8 のバイト数)
print(len("😀")) # 1(絵文字 1 つは 1 コードポイント)
print(len("🇯🇵")) # 2(国旗は 2 つのコードポイントの組)
print(len("e\u0301")) # 2(é を e と結合文字で表した場合)len は文字列に含まれるコードポイントの数を返す。
バイト数が必要なときは encode してから len を取る。
国旗や結合文字は見た目が 1 文字でも複数のコードポイントでできているので、len の値と見た目の文字数は一致しない。
JavaScript 実行確認済み
const s = "こんにちは";
console.log(s.length); // 5
console.log(new TextEncoder().encode(s).length); // 15(UTF-8 のバイト数)
console.log("😀".length); // 2(UTF-16 のサロゲートペア)
console.log([..."😀"].length); // 1(コードポイントの数)
const seg = new Intl.Segmenter("ja", { granularity: "grapheme" });
console.log([..."🇯🇵"].length); // 2(国旗は 2 つのコードポイント)
console.log([...seg.segment("🇯🇵")].length); // 1(見た目の 1 文字)String.length は UTF-16 の単位数を返すため、絵文字のようにサロゲートペアになる文字は 2 と数えられる。
コードポイントで数えるなら展開してから length を見て、見た目の 1 文字で数えるなら Intl.Segmenter を使う。
バイト数は TextEncoder で UTF-8 に変換して取る。
TypeScript 実行確認済み
const segmenter = new Intl.Segmenter("ja", { granularity: "grapheme" });
function countGraphemes(text: string): number {
return [...segmenter.segment(text)].length;
}
const flag: string = "🇯🇵";
console.log(flag.length); // 4(UTF-16 の単位)
console.log([...flag].length); // 2(コードポイントの数)
console.log(countGraphemes(flag)); // 1(見た目の 1 文字)
console.log(countGraphemes("こんにちは")); // 5実行時の振る舞いは JavaScript と同じで、差分は戻り値を number と型付けして関数に切り出している点だけである。
Intl.Segmenter は lib に es2022 以降を含めないと型が見つからないので、tsconfig の target か lib を確認する。
Go 実行確認済み
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
s := "こんにちは"
fmt.Println(len(s)) // 15(バイト数)
fmt.Println(utf8.RuneCountInString(s)) // 5(文字数。rune の数)
fmt.Println(len([]rune(s))) // 5(rune に変換して数える)
fmt.Println(len("😀")) // 4(絵文字は 4 バイト)
fmt.Println(utf8.RuneCountInString("😀")) // 1
}len(s) は文字数ではなくバイト数を返すため、日本語は 1 文字 3 バイトで数えられる。
文字数が必要なときは utf8.RuneCountInString を使う。
[]rune に変換しても数えられるが、変換のためにメモリを確保する分だけ無駄が出る。
Rust 実行確認済み
fn main() {
let s = "こんにちは";
println!("{}", s.len()); // 15(UTF-8 のバイト数)
println!("{}", s.chars().count()); // 5(Unicode スカラー値の数)
println!("{}", "😀".len()); // 4(絵文字は 4 バイト)
println!("{}", "😀".chars().count()); // 1
}str::len は UTF-8 のバイト数を返し、文字数は chars().count() で求める。
chars().count() は先頭から走査するので文字列の長さに比例した時間がかかる。
見た目の 1 文字(書記素クラスタ)で数える機能は標準ライブラリに無く、unicode-segmentation クレートなどを使う。
Java 実行確認済み
import java.nio.charset.StandardCharsets;
public class Main {
public static void main(String[] args) {
String s = "こんにちは";
String emoji = "😀";
System.out.println(s.length()); // 5
System.out.println(s.getBytes(StandardCharsets.UTF_8).length); // 15(UTF-8 のバイト数)
System.out.println(emoji.length()); // 2(UTF-16 の char 単位)
System.out.println(emoji.codePointCount(0, emoji.length())); // 1(コードポイントの数)
}
}String.length は UTF-16 の char 単位数を返すので、絵文字などの補助文字は 2 と数えられる。
コードポイントの数は codePointCount で求める。
バイト数は文字コードを明示して getBytes から取り、引数なしの getBytes は実行環境の既定の文字コードに依存するため避ける。
C# 実行確認済み
using System;
using System.Globalization;
using System.Linq;
using System.Text;
class Program {
static void Main() {
var s = "こんにちは";
Console.WriteLine(s.Length); // 5
Console.WriteLine(Encoding.UTF8.GetByteCount(s)); // 15(UTF-8 のバイト数)
Console.WriteLine("😀".Length); // 2(UTF-16 の char 単位)
Console.WriteLine("😀".EnumerateRunes().Count()); // 1(コードポイントの数)
Console.WriteLine(new StringInfo("🇯🇵").LengthInTextElements); // 1(見た目の 1 文字)
}
}string.Length は UTF-16 の char 単位数を返すため、絵文字は 2 と数えられる。
コードポイントの数は EnumerateRunes で、見た目の 1 文字は StringInfo.LengthInTextElements で数える。
バイト数は Encoding.UTF8.GetByteCount で求める。
つまずき
「長さ」が何を数えるかは言語ごとに違う。
Python の len はコードポイントの数、Go と Rust の len はバイト数、JavaScript・Java・C# の length は UTF-16 の単位数を返す。
実行して比べると、「こんにちは」は Python・JavaScript・Java・C# では 5、Go と Rust の len では UTF-8 のバイト数である 15 になった。
「😀」はさらに分かれ、Python が 1、JavaScript・Java・C# が 2、Go・Rust が 4 を返した。
日本語の文字数を数えたいだけなら Go では utf8.RuneCountInString、Rust では chars().count() を選ぶ。
見た目の1文字と数値が一致しない
国旗の絵文字「🇯🇵」は見た目が 1 文字だが、2 つのコードポイントの組でできている。
実行すると Python の len と JavaScript のコードポイント展開はどちらも 2 を返し、Intl.Segmenter と C# の StringInfo は 1 を返した。
é を e と結合文字で表した場合も Python の len は 2 になる。
入力欄の「最大 N 文字」のような上限を数えるときは、バイト・コードポイント・見た目の 1 文字のどれで数えるかを先に決め、保存先の列長の単位と揃えておく。