Java Tips | 基本ユーティリティ:ファイルサイズ取得

Java Java
スポンサーリンク

ファイルサイズ取得は「重さを意識した設計」をするための技

業務システムでは、「このファイル、本当にメモリに全部載せて大丈夫?」「アップロードされたファイルが想定より巨大じゃない?」
といった“重さ”の感覚がとても大事になります。

その入口になるのが「ファイルサイズ取得」です。
単に「何バイトか知る」だけでなく、「上限チェック」「ログ出力」「処理方針の分岐(全部読むか、ストリームで読むか)」など、設計の判断材料になります。


基本:Files.size(Path) でバイト数を取得する

一番シンプルなサンプル

Java 7 以降なら、java.nio.file.Files.size を使うのが基本です。

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public class FileSizeBasic {

    public static void main(String[] args) throws IOException {
        Path path = Path.of("data/input.csv");
        long size = Files.size(path);

        System.out.println("size (bytes) = " + size);
    }
}
Java

Files.size(path) は、そのパスが指すファイルのサイズを「バイト数」で返します。
戻り値は long なので、2GB を超えるような大きなファイルでも扱えます。

ここでの重要ポイントは、「単位は“バイト”である」ことです。
人間にとっては KB、MB、GB のほうが分かりやすいので、実務では「バイト → 人間向け表記」に変換するユーティリティとセットで使うことが多いです。


実務で使える「ファイルサイズ取得ユーティリティ」の最小形

size と、人間向けの表示をセットにする

まずは、サイズ取得と表示用フォーマットを一箇所にまとめたユーティリティを作ってみます。

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public final class FileSizes {

    private FileSizes() {}

    public static long size(Path path) throws IOException {
        return Files.size(path);
    }

    public static String humanReadable(long bytes) {
        if (bytes < 1024) {
            return bytes + " B";
        }
        double kb = bytes / 1024.0;
        if (kb < 1024) {
            return String.format("%.1f KB", kb);
        }
        double mb = kb / 1024.0;
        if (mb < 1024) {
            return String.format("%.1f MB", mb);
        }
        double gb = mb / 1024.0;
        return String.format("%.1f GB", gb);
    }

    public static String describe(Path path) throws IOException {
        long size = size(path);
        return path + " (" + humanReadable(size) + ")";
    }
}
Java

使う側はこう書けます。

Path path = Path.of("data/input.csv");
long size = FileSizes.size(path);
System.out.println("size = " + size + " bytes");
System.out.println("human = " + FileSizes.humanReadable(size));
System.out.println("desc  = " + FileSizes.describe(path));
Java

ここで深掘りしたいポイントは、「“生のバイト数”と“人間向け表記”を分けて扱う」ことです。
ロジック(上限チェックなど)はバイト数で行い、ログや画面表示は人間向け表記を使う、という役割分担にすると、コードが読みやすくなります。


例題:アップロードファイルのサイズ上限チェック

「大きすぎるファイルはそもそも受け付けない」

よくある要件として、「アップロードできるファイルサイズは最大 10MB まで」といった制限があります。
これをサーバー側でチェックするユーティリティを考えてみます。

ここでは「一度ファイルとして保存された後にチェックする」パターンにします。

import java.io.IOException;
import java.nio.file.Path;

public final class UploadValidator {

    private UploadValidator() {}

    private static final long MAX_SIZE_BYTES = 10L * 1024 * 1024; // 10MB 相当

    public static void validateSize(Path uploadedFile) throws IOException {
        long size = FileSizes.size(uploadedFile);
        if (size > MAX_SIZE_BYTES) {
            throw new IllegalArgumentException(
                    "アップロードファイルが大きすぎます: " +
                    FileSizes.humanReadable(size) +
                    " (上限 " + FileSizes.humanReadable(MAX_SIZE_BYTES) + ")");
        }
    }
}
Java

使う側はこうです。

Path uploaded = Path.of("work/upload.tmp");
UploadValidator.validateSize(uploaded);
// ここから先は「サイズ上限を満たしている」前提で処理を書ける
Java

ここでの重要ポイントは、「上限値もバイト数で持ち、メッセージだけ人間向け表記にしている」ことです。
10 * 1024 * 1024 のように「バイト数としての上限」をコードに書いておくと、
「12MB に変えたい」といったときも、計算がしやすくなります。

また、例外メッセージに「実際のサイズ」と「上限」を両方、人間向け表記で含めておくと、
ログを見たときに「どれくらいオーバーしていたのか」が一目で分かります。


例題:大きなファイルは「読み方」を変える

サイズを見て「全部読むか、ストリームで読むか」を決める

例えば、「CSV ファイルを読み込んで処理する」というバッチを考えます。
小さなファイルなら全部メモリに読み込んでも問題ありませんが、数百 MB のファイルを丸ごと読み込むと、メモリを圧迫します。

そこで、「ファイルサイズを見て、読み方を変える」という設計ができます。

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

public class CsvProcessor {

    private static final long THRESHOLD_BYTES = 5L * 1024 * 1024; // 5MB

    public void process(Path csv) throws IOException {
        long size = FileSizes.size(csv);
        System.out.println("processing " + FileSizes.describe(csv));

        if (size <= THRESHOLD_BYTES) {
            processSmall(csv);
        } else {
            processLarge(csv);
        }
    }

    private void processSmall(Path csv) throws IOException {
        List<String> lines = Files.readAllLines(csv);
        // 全行メモリに載せて処理
    }

    private void processLarge(Path csv) throws IOException {
        try (var stream = Files.lines(csv)) {
            stream.forEach(line -> {
                // 1 行ずつストリームで処理
            });
        }
    }
}
Java

ここで深掘りしたいのは、「ファイルサイズ取得を“設計の分岐条件”として使っている」点です。
単に「大きいか小さいかを知る」だけでなく、「読み方を変える」「処理方法を変える」判断材料にしているわけです。

この感覚を持てると、「とりあえず全部 readAllBytes」から一歩抜け出して、
「サイズを見てから戦略を選ぶ」という、実務寄りの設計ができるようになります。


ディレクトリ配下の合計サイズを出す

Files.walk と組み合わせて「フォルダの重さ」を測る

「このディレクトリ配下、全部でどれくらいのサイズか知りたい」という場面もあります。
バックアップ対象の重さを見積もったり、古いログを消す基準にしたりするイメージです。

import java.io.IOException;
import java.nio.file.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Stream;

public final class DirectorySizes {

    private DirectorySizes() {}

    public static long totalSize(Path root) throws IOException {
        if (root == null || !Files.exists(root)) {
            return 0L;
        }
        AtomicLong total = new AtomicLong(0L);

        try (Stream<Path> stream = Files.walk(root)) {
            stream
                .filter(Files::isRegularFile)
                .forEach(p -> {
                    try {
                        total.addAndGet(Files.size(p));
                    } catch (IOException e) {
                        throw new RuntimeException("サイズ取得中にエラー: " + p, e);
                    }
                });
        }
        return total.get();
    }
}
Java

使う側はこうです。

Path logsDir = Path.of("logs");
long total = DirectorySizes.totalSize(logsDir);
System.out.println("logs total = " + FileSizes.humanReadable(total));
Java

ここでのポイントは、「ファイルサイズ取得は“単体”だけでなく、“合計”にも使える」ということです。
Files.walk と組み合わせると、「このフォルダ、実は 10GB あるじゃん」という現実を数字で突きつけてくれます。
そこから「古い日付のログを消そう」「圧縮しよう」といった次のアクションにつなげられます。


ファイルサイズ取得の“落とし穴”と注意点

サイズ取得も I/O であり、コストゼロではない

Files.size はメタデータを読むだけなので、ファイル内容を全部読むよりは軽いですが、
それでもファイルシステムへのアクセスが発生する I/O です。

大量のファイルに対して一気に Files.size を呼ぶと、それなりに時間がかかります。
監視や定期バッチで「ディレクトリ配下の合計サイズ」を頻繁に計算する場合は、
実行タイミングや頻度を考えたり、結果をキャッシュしたりする工夫も検討したほうがよいです。

ネットワークドライブやクラウドストレージでは揺らぎがある

NFS やクラウドストレージ(マウントされたオブジェクトストレージなど)の場合、
サイズ取得が遅かったり、一時的にエラーになったりすることがあります。

実務では、「サイズ取得に失敗したらどうするか」を決めておくことが大事です。
「失敗したら 0 とみなす」のか、「処理自体をエラーにする」のか、「警告ログだけ出して続行する」のか、
ユーティリティ側や呼び出し側で方針をはっきりさせておくと、運用で迷いません。


まとめ:ファイルサイズ取得ユーティリティで身につけたい感覚

ファイルサイズ取得は、「ただ数字を知る」ためではなく、「その数字をもとに設計や運用の判断をする」ための技です。

押さえておきたい感覚はこうです。

ファイルサイズは Files.size(Path) でバイト数として取得し、ロジックはバイト単位で書く。
人間向けには KB/MB/GB に変換するユーティリティを用意し、ログやメッセージで使う。
アップロードやバッチ処理では、「サイズ上限チェック」や「読み方の切り替え」の判断材料として使う。
ディレクトリ配下の合計サイズを出すことで、「どこがディスクを食っているか」を数字で把握する。
サイズ取得も I/O であり、失敗や遅延の可能性がある前提で、エラー時の扱いを設計しておく。

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