C# Tips | ファイル・ディレクトリ操作:ファイル文字数カウント

C sharp C#
スポンサーリンク

はじめに 「文字数カウント」は“なんとなく”で書くと危ない

「ファイルの文字数を数える」――これも一見シンプルですが、実務では意外と奥が深いです。

文字数でバリデーションしたい(例:1 万文字を超える説明文は NG)。
レポートやログの統計を取りたい(1 日あたり何文字くらい書かれているか)。
外部システムの制限(「この API は最大 5000 文字まで」など)に合わせて事前チェックしたい。

ここで雑に実装すると、「バイト数」と「文字数」を混同したり、「マルチバイト文字(日本語)」でズレたりします。
なので、「何を数えたいのか」をはっきりさせたうえで、C# の文字列とエンコーディングの仕組みに沿って実装することが大事です。

ここでは、プログラミング初心者向けに、「文字数カウントの基本」「エンコーディングの考え方」「大きなファイルへの対応」まで、例題付きで丁寧に解説します。


まず整理:バイト数と文字数はまったく別物

「ファイルサイズ(バイト数)」と「文字数」の違い

よくある勘違いが、「ファイルサイズ(バイト数)=文字数」だと思ってしまうことです。

例えば、UTF-8 のテキストファイルで「こんにちは」と書いた場合、
文字数は 5 文字ですが、バイト数はもっと多くなります(日本語 1 文字が 3 バイトなど)。

FileInfo.Length で取れるのは「バイト数」であって、「文字数」ではありません。

var info = new FileInfo(@"C:\data\hello.txt");
Console.WriteLine(info.Length); // これはバイト数
C#

「文字数」を知りたいなら、一度テキストとして読み込んで、文字列として数える必要があります。


一番シンプルな文字数カウント 小さいファイル向け

File.ReadAllText で全部読み込んでから数える

ファイルが小さい(数 KB〜数 MB 程度)なら、まずはこれで十分です。

using System;
using System.IO;
using System.Text;

public static class CharCountUtil
{
    public static int CountChars(string path, Encoding encoding)
    {
        if (!File.Exists(path))
        {
            throw new FileNotFoundException("ファイルが存在しません。", path);
        }

        string text = File.ReadAllText(path, encoding);

        return text.Length;
    }
}
C#

使い方の例です。

string path = @"C:\data\message.txt";

int count = CharCountUtil.CountChars(path, Encoding.UTF8);

Console.WriteLine($"文字数: {count}");
C#

ここでの重要ポイントは二つです。

1つ目は、「エンコーディングを明示している」こと。
UTF-8 なのか Shift_JIS なのかで、バイト列→文字列の変換結果が変わります。
ファイルのエンコーディングが分かっているなら、必ず指定しましょう。

2つ目は、「text.Length は“C# の文字数”」だということ。
C# の string は UTF-16 で、Length は「UTF-16 のコード単位数」です。
ほとんどの日本語テキストでは「見た目の文字数」と一致しますが、絵文字や一部の特殊文字ではズレることがあります(ここは後で少し触れます)。


大きなファイル向け:全部読み込まずに文字数を数える

ReadLines で行ごとに読みながら合計する

数百 MB のログや巨大な CSV を扱うとき、ReadAllText で全部読み込むのは危険です。
メモリを大量に消費して、最悪落ちます。

その場合は、「少しずつ読みながら数える」スタイルに切り替えます。
テキストファイルなら、行単位で読みながら文字数を足していくのがシンプルです。

using System;
using System.IO;
using System.Linq;
using System.Text;

public static class CharCountUtil
{
    public static long CountCharsStreaming(string path, Encoding encoding)
    {
        if (!File.Exists(path))
        {
            throw new FileNotFoundException("ファイルが存在しません。", path);
        }

        long total = 0;

        foreach (string line in File.ReadLines(path, encoding))
        {
            total += line.Length;
        }

        return total;
    }
}
C#

使い方はこんな感じです。

string path = @"C:\logs\big.log";

long count = CharCountUtil.CountCharsStreaming(path, Encoding.UTF8);

Console.WriteLine($"文字数: {count}");
C#

ここでの重要ポイントは、「戻り値を long にしている」ことです。
巨大ファイルだと、文字数が int の上限(約 21 億)を超える可能性があります。
最初から long で設計しておくと安心です。

改行を文字数に含めるかどうか

上の実装では、ReadLines を使っているため、改行文字は含まれていません
ReadLines は行末の \r\n\n を取り除いて返すからです。

「改行も含めて文字数を数えたい」場合は、ルールを決める必要があります。

例えば、「行末に 1 文字分の改行をカウントする」と決めるなら、こう書けます。

public static long CountCharsWithNewLine(string path, Encoding encoding)
{
    if (!File.Exists(path))
    {
        throw new FileNotFoundException("ファイルが存在しません。", path);
    }

    long total = 0;
    long lineCount = 0;

    foreach (string line in File.ReadLines(path, encoding))
    {
        total += line.Length;
        lineCount++;
    }

    total += lineCount; // 各行の末尾に 1 文字分の改行とみなす

    return total;
}
C#

「改行を含めるかどうか」は業務要件によって変わるので、
ユーティリティの名前やコメントで「どちらの数え方か」を明示しておくと、後で混乱しません。


「見た目の文字数」と「string.Length」のズレ問題

絵文字・結合文字・サロゲートペア

C# の string.Length は、「UTF-16 のコード単位数」です。
ほとんどの日本語(ひらがな・カタカナ・漢字)は 1 文字=1 コード単位なので、
Length と「見た目の文字数」は一致します。

ただし、次のようなケースではズレることがあります。

絵文字(😀 など)
一部の特殊記号
結合文字(アクセント付き文字など)

例えば、絵文字 1 文字が Length == 2 になることがあります。
これは「サロゲートペア」と呼ばれる仕組みのせいです。

実務で日本語テキスト中心なら、そこまで気にしなくても困らないことが多いですが、
「SNS っぽいメッセージ」「絵文字多めの入力」を扱う場合は、
「見た目の文字数」を数えたいのか、「UTF-16 コード単位数」を数えたいのかを意識したほうがいいです。

初心者向けの現場では、まずは「string.Length を文字数として扱う」で十分です。
「絵文字込みで厳密にカウントしたい」ような要件が出てきたら、そのときに System.Globalization.StringInfo などを使う方向を検討すればOKです。


「バイト数」と「文字数」を両方知りたい場合

文字数+バイト数をセットで返すユーティリティ

業務では、「API の制限は“文字数”だけど、DB のカラム制限は“バイト数”」みたいなこともあります。
その場合、「文字数」と「バイト数」を両方知りたいことがあります。

例えば、こんなユーティリティを用意しておくと便利です。

using System;
using System.IO;
using System.Text;

public sealed class TextSizeInfo
{
    public long CharCount { get; }
    public long ByteCount { get; }

    public TextSizeInfo(long charCount, long byteCount)
    {
        CharCount = charCount;
        ByteCount = byteCount;
    }
}

public static class TextSizeUtil
{
    public static TextSizeInfo GetTextSize(string path, Encoding encoding)
    {
        if (!File.Exists(path))
        {
            throw new FileNotFoundException("ファイルが存在しません。", path);
        }

        string text = File.ReadAllText(path, encoding);

        long charCount = text.Length;
        long byteCount = encoding.GetByteCount(text);

        return new TextSizeInfo(charCount, byteCount);
    }
}
C#

使い方の例です。

string path = @"C:\data\comment.txt";

var info = TextSizeUtil.GetTextSize(path, Encoding.UTF8);

Console.WriteLine($"文字数: {info.CharCount}");
Console.WriteLine($"バイト数(UTF-8): {info.ByteCount}");
C#

ここでのポイントは、「バイト数もエンコーディング依存」だということです。
同じ文字列でも、UTF-8 と Shift_JIS ではバイト数が変わります。


例外・エラー処理をどう考えるか

ファイルがない・読めないときの扱い

行数カウントのときと同じく、「ファイルが存在しない」「権限がない」などのケースをどう扱うかを決める必要があります。

基本パターンは二つです。

例外を投げる(FileNotFoundException など)
→ 呼び出し側でログを出したり、ユーザーにエラーを伝えたりする。

0 を返す・特別な値を返す
→ 「失敗したら 0 でいい」と割り切れる場面向け。

初心者向けには、まずは「例外をそのまま投げる」設計のほうが分かりやすいです。
業務で使うときに、「失敗時の扱い」をチームで決めてから、Try〜 系のラッパーを追加していくとよいです。


まとめ 実務で使える「ファイル文字数カウント」ユーティリティの考え方

文字数カウントはシンプルに見えて、実は「何を数えるか」をはっきりさせることが一番大事です。

押さえておきたいポイントを整理すると、こうなります。

  • FileInfo.Length は「バイト数」であって「文字数」ではない。
  • 文字数を数えるには、テキストとして読み込んで string.Length を使う。
  • 大きなファイルでは ReadAllText ではなく ReadLines などで「少しずつ読みながら」数える。
  • 改行を文字数に含めるかどうかは要件次第。どちらかに決めて、ユーティリティの仕様として明示する。
  • 戻り値は long にしておくと、大量文字でも安全。
  • 絵文字などで「見た目の文字数」と Length がズレることがあるが、日本語中心の業務ならまずは Length ベースで十分。

ここまで押さえておけば、「なんとなく ReadAllText して Length を見ました」から一歩進んで、
「業務要件に合わせた、ちゃんと意味のある文字数カウントユーティリティ」を設計できるようになります。

タイトルとURLをコピーしました