Kotlin の標準入力は readln() で1行を文字列として読み、toInt() や split(" ") で数値やリストに変換するのが基本です。入力の終わりまで読むときは、null を返す readlnOrNull() を使います。

この記事では、入力の形ごとのコピペ用コードを早見表にまとめ、3つの読み方(readln+split、BufferedReader+StringTokenizer、自前のバイト読み取り)を同じ100万行の入力で測った時間と、出力のまとめ方による時間の差を載せます。空行や末尾の空白で出る例外も、実際の文言と直し方をセットで示します。

確認環境:Ubuntu 24.04 / Kotlin 2.4.20(kotlinc)/ OpenJDK 21.0.10(2コア)。readln と readlnOrNull は Kotlin 1.6 から使える関数です。

Kotlin 標準入力の早見表

入力の形 入力例 コード
1行に整数が1個 3 val n = readln().toInt()
1行に文字列が1個 hello val s = readln()
1行に整数が2個 10 20 val (a, b) = readln().split(" ").map { it.toInt() }
1行に整数が複数 1 2 3 val xs = readln().split(" ").map { it.toInt() }
型が混ざった1行 apple 5 val (name, countText) = readln().split(" ") のあと countText.toInt()
N 行に1個ずつ 3行の数 val rows = List(n) { readln().toLong() }
N 行 M 列の数の表 2行3列 val grid = List(h) { readln().split(" ").map { it.toInt() } }
N 行の文字の盤面 #.# など val board = List(h) { readln().toCharArray() }
個数不明(EOF まで) 何行でも val rest = generateSequence(::readlnOrNull).toList()
大きな数 3000000000 readln().toLong()
小数 3.14 readln().toDouble()
関数 入力が終わっているとき 使いどころ
readln() 例外(ReadAfterEOFException) 行があると分かっている入力
readlnOrNull() null を返す 入力の終わりまで読むループ
readLine() null を返す Kotlin 1.6 より前から使われている書き方。2.4 のコンパイラは readln / readlnOrNull への置き換えを案内する

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Kotlin 標準入力の書き方 readln・split と高速入力」

入力の形ごとの完成形コード

早見表のコードを1つのプログラムにまとめました。shapes.kt として保存します。

fun main() {
    // 1. 1行に整数が1個
    val n = readln().toInt()

    // 2. 1行に文字列が1個
    val s = readln()

    // 3. 1行に整数が2個(空白区切り)
    val (a, b) = readln().split(" ").map { it.toInt() }

    // 4. 1行に整数が複数(個数は n)
    val xs = readln().split(" ").map { it.toInt() }

    // 5. 型が混ざった1行(名前と数)
    val (name, countText) = readln().split(" ")
    val count = countText.toInt()

    // 6. N 行に1個ずつ
    val rows = List(n) { readln().toLong() }

    // 7. N 行 M 列(数の表)
    val (h, w) = readln().split(" ").map { it.toInt() }
    val grid = List(h) { readln().split(" ").map { it.toInt() } }

    // 8. N 行の文字の盤面
    val board = List(h) { readln().toCharArray() }

    // 9. 個数が分からず、入力の終わり(EOF)まで読む
    val rest = generateSequence(::readlnOrNull).toList()

    println("n=$n s=$s a=$a b=$b")
    println("xs=$xs sum=${xs.sum()}")
    println("name=$name count=$count")
    println("rows=$rows")
    println("h=$h w=$w grid=$grid")
    println("board=" + board.map { it.concatToString() } + " board[1][2]=" + board[1][2])
    println("rest=$rest")
}

入力ファイル in1.txt の中身です。

3
hello
10 20
1 2 3
apple 5
100
200
300
2 3
1 2 3
4 5 6
#.#
..#
foo
bar
baz

コンパイルして、ファイルを標準入力に流し込みます。-Dstdout.encoding=UTF-8 は日本語の出力を UTF-8 で表示するための指定です。

kotlinc shapes.kt -include-runtime -d shapes.jar
java -Dstdout.encoding=UTF-8 -jar shapes.jar < in1.txt
n=3 s=hello a=10 b=20
xs=[1, 2, 3] sum=6
name=apple count=5
rows=[100, 200, 300]
h=2 w=3 grid=[[1, 2, 3], [4, 5, 6]]
board=[#.#, ..#] board[1][2]=#
rest=[foo, bar, baz]

読み方の考え方は3段階です。

  1. readln() で1行を String として受け取る。
  2. 数値なら toInt()・toLong()・toDouble() で変換する。 複数あるなら split(" ") でリストにしてから map で変換する。
  3. 行数が決まっていれば List(n) { ... } で繰り返す。 ブロックが n 回呼ばれ、結果がリストになります。

val (a, b) = ... は分解宣言という書き方で、リストの先頭から順に変数へ入れます。5個まで並べられます。

個数が分からない入力を EOF まで読む

行数が先に与えられない入力は、readlnOrNull() が null を返すまで読みます。

fun main() {
    // 方法1:while と readlnOrNull
    var sum = 0L
    var lines = 0
    while (true) {
        val line = readlnOrNull() ?: break
        if (line.isBlank()) continue
        sum += line.trim().split(Regex("\\s+")).sumOf { it.toLong() }
        lines++
    }
    println("行数=$lines 合計=$sum")
}
fun main() {
    // 方法2:generateSequence で行の Sequence にする
    val total = generateSequence(::readlnOrNull)
        .filter { it.isNotBlank() }
        .flatMap { it.trim().split(Regex("\\s+")) }
        .sumOf { it.toLong() }
    println("合計=$total")

    // 方法3:System.in を最後まで読む
    // val all = System.`in`.bufferedReader().readText()
    // val lines = System.`in`.bufferedReader().readLines()
}

printf '1 2 3\n\n10 20\n5\n' をパイプで渡した結果です(2行目は空行、3行目は空白が3つ)。

行数=3 合計=41
合計=41

a ?: break は「null なら break」という書き方です。キーボードから入力しているときは、Linux や macOS では Ctrl+D が入力の終わりの合図になります。

空行・末尾の空白で出る例外と対処

入力に空行や余分な空白があると、変換のところで止まります。実際の例外文を並べます。

fun show(label: String, block: () -> Any?) {
    try {
        println("$label -> ${block()}")
    } catch (e: Exception) {
        println("$label\n  ${e::class.qualifiedName}: ${e.message}")
    }
}

fun main() {
    // 入力は「空行」「末尾に空白」「空白が2つ」「先頭に空白」「数字以外」「桁あふれ」の6行
    show("空行を toInt") { readln().toInt() }
    show("末尾に空白 split(\" \")") { readln().split(" ").map { it.toInt() } }
    show("空白が2つ split(\" \")") { readln().split(" ").map { it.toInt() } }
    show("先頭に空白 toInt") { readln().toInt() }
    show("数字以外 toInt") { readln().toInt() }
    show("桁あふれ toInt") { readln().toInt() }
    show("入力が終わった後の readln") { readln() }
    show("入力が終わった後の readlnOrNull") { readlnOrNull() }
    show("入力が終わった後の readLine()!!") { readLine()!! }
    println("RuntimeException の一種か: " + (runCatching { readln() }.exceptionOrNull() is RuntimeException))
}

入力 in_err.txt は次の6行です(1行目は空行、2行目は末尾に空白、3行目は空白が2つ、4行目は先頭に空白)。

printf '\n1 2 3 \n1  2\n 42\n12a\n3000000000\n' > in_err.txt
java -Dstdout.encoding=UTF-8 -jar err.jar < in_err.txt
空行を toInt
  java.lang.NumberFormatException: For input string: ""
末尾に空白 split(" ")
  java.lang.NumberFormatException: For input string: ""
空白が2つ split(" ")
  java.lang.NumberFormatException: For input string: ""
先頭に空白 toInt
  java.lang.NumberFormatException: For input string: " 42"
数字以外 toInt
  java.lang.NumberFormatException: For input string: "12a"
桁あふれ toInt
  java.lang.NumberFormatException: For input string: "3000000000"
入力が終わった後の readln
  kotlin.io.ReadAfterEOFException: EOF has already been reached
入力が終わった後の readlnOrNull -> null
入力が終わった後の readLine()!!
  java.lang.NullPointerException: null
RuntimeException の一種か: true

catch しない場合、readln の例外は次のように表示されます(shapes.jar に空の入力 < /dev/null を渡したときの出力)。

Exception in thread "main" kotlin.io.ReadAfterEOFException: EOF has already been reached
    at kotlin.io.ConsoleKt.readln(Console.kt:152)
    at ShapesKt.main(shapes.kt:3)
    at ShapesKt.main(shapes.kt)
例外文 原因 対処
NumberFormatException: For input string: "" 空行を変換した、または split(" ") の結果に空文字が混ざった trim() してから分ける、split(Regex("\\s+")) を使う、空の要素を filter で除く
NumberFormatException: For input string: " 42" 前後に空白が付いている trim().toInt()
NumberFormatException: For input string: "12a" 数字以外が混ざっている toIntOrNull() で null を受けて既定値にする
NumberFormatException: For input string: "3000000000" Int の範囲(約±21億)を超えた toLong()
ReadAfterEOFException: EOF has already been reached 行が残っていないのに readln() を呼んだ 読む回数を見直す、readlnOrNull() に替える
NullPointerException readLine()!! が null を受けた readln() か readlnOrNull() に替える

ReadAfterEOFException は RuntimeException の一種です(最後の行の出力で確認できます)。readLine()!! を含むコードを Kotlin 2.4 でコンパイルすると、次の案内が表示されます。

err.kt:19:38: warning: 'fun readLine(): String?' is deprecated. Use `readln()` instead of `readLine()!!`, and `readlnOrNull()` instead of `readLine()`.

直したコードと、同じ入力での結果です。

fun main() {
    // 入力は err.kt と同じ6行
    readln()                                                      // 空行は読み飛ばす
    println(readln().trim().split(" ").map { it.toInt() })        // 末尾の空白は trim
    println(readln().trim().split(Regex("\\s+")).map { it.toInt() }) // 連続する空白は \s+
    println(readln().trim().toInt())                              // 先頭の空白も trim
    println(readln().toIntOrNull() ?: -1)                         // 数字以外は toIntOrNull
    println(readln().toLong())                                    // 大きな数は Long
    println(readlnOrNull() ?: "(入力の終わり)")                    // EOF は readlnOrNull
    // split() に空白文字の判定を渡す書き方でも、空の要素を除ける
    println(" 1  2 3 ".split(" ").filter { it.isNotEmpty() }.map { it.toInt() })
}
[1, 2, 3]
[1, 2]
42
-1
3000000000
(入力の終わり)
[1, 2, 3]

迷ったら readln().trim().split(Regex("\\s+")) と書いておくと、前後の空白と連続する空白の両方に対応できます。

高速入力:3つの方式を100万行で測る

入力が数十万行を超えると、読み方で時間に差が出ます。1行に整数が2個ある行を100万行(約 20 MB)用意し、全部の合計を求める処理で比べました。

入力の生成(Python 3.11):

import random
random.seed(1)
N=1_000_000
with open('big.txt','w') as f:
    f.write(f"{N}\n")
    for _ in range(N):
        f.write(f"{random.randint(-10**9,10**9)} {random.randint(-10**9,10**9)}\n")
import java.io.BufferedReader
import java.io.DataInputStream
import java.io.InputStreamReader
import java.util.StringTokenizer
import kotlin.time.measureTimedValue

// 方式1:readln + split
fun byReadln(): Long {
    val n = readln().toInt()
    var sum = 0L
    repeat(n) {
        val (a, b) = readln().split(" ").map { it.toInt() }
        sum += a.toLong() + b
    }
    return sum
}

// 方式2:BufferedReader + StringTokenizer
fun byTokenizer(): Long {
    val br = BufferedReader(InputStreamReader(System.`in`))
    val n = br.readLine().toInt()
    var sum = 0L
    repeat(n) {
        val st = StringTokenizer(br.readLine())
        sum += st.nextToken().toLong() + st.nextToken().toInt()
    }
    return sum
}

// 方式3:自前のバイト読み取り
class FastReader(stream: java.io.InputStream) {
    private val input = DataInputStream(stream)
    private val buf = ByteArray(1 shl 16)
    private var len = 0
    private var ptr = 0

    private fun readByte(): Int {
        if (ptr == len) {
            len = input.read(buf, 0, buf.size)
            ptr = 0
            if (len <= 0) return -1
        }
        return buf[ptr++].toInt()
    }

    fun nextLong(): Long {
        var c = readByte()
        while (c != -1 && c <= ' '.code) c = readByte()   // 空白と改行を読み飛ばす
        val negative = c == '-'.code
        if (negative) c = readByte()
        var x = 0L
        while (c >= '0'.code && c <= '9'.code) {
            x = x * 10 + (c - '0'.code)
            c = readByte()
        }
        return if (negative) -x else x
    }

    fun nextInt(): Int = nextLong().toInt()
}

fun byBytes(): Long {
    val fr = FastReader(System.`in`)
    val n = fr.nextInt()
    var sum = 0L
    repeat(n) { sum += fr.nextLong() + fr.nextLong() }
    return sum
}

fun main(args: Array<String>) {
    val (sum, time) = measureTimedValue {
        when (args[0]) {
            "readln" -> byReadln()
            "tokenizer" -> byTokenizer()
            else -> byBytes()
        }
    }
    println("${args[0]}: sum=$sum time=${time.inWholeMilliseconds} ms")
}
kotlinc bench.kt -include-runtime -d bench.jar
java -jar bench.jar readln < big.txt
java -jar bench.jar tokenizer < big.txt
java -jar bench.jar bytes < big.txt

1回の実行の出力例です。3方式とも同じ合計になっています。

readln: sum=-1287932221238 time=2926 ms
tokenizer: sum=-1287932221238 time=1151 ms
bytes: sum=-1287932221238 time=297 ms

3回ずつ実行した結果です。

方式 1回目 2回目 3回目 特徴
readln + split 2926 ms 3139 ms 2817 ms いちばん短く書ける。10万行くらいまでなら十分
BufferedReader + StringTokenizer 1151 ms 1666 ms 690 ms 行ごとのリストを作らないぶん速い。書き方も覚えやすい
自前のバイト読み取り 297 ms 435 ms 275 ms 文字列を作らずに数値へ変換する。コードは長い

この環境では、readln+split に対して StringTokenizer がおよそ2〜4倍、バイト読み取りがおよそ7〜10倍速い結果でした。 数値はマシンや JVM の状態で変わるので、順番と桁の違いを目安にしてください。

選び方の目安です。

入力の量 おすすめ
数千行まで readln + split(読みやすさを優先)
10万〜100万行 BufferedReader + StringTokenizer
100万行を超える、制限時間が厳しい 自前のバイト読み取り(FastReader をひな形として貼る)

BufferedReader と readln を同じプログラムで混ぜると、BufferedReader が先読みした分を readln 側が受け取れなくなります。1つのプログラムでは読み方を1種類に揃えます。

高速出力:println の繰り返しとまとめ書きを比べる

出力も、1行ずつ println するか、まとめて出すかで時間が変わります。0 から 999999 までの100万行を出力して測りました。

import java.io.BufferedWriter
import java.io.OutputStreamWriter
import java.io.PrintWriter
import kotlin.time.measureTime

fun main(args: Array<String>) {
    val n = 1_000_000
    val time = measureTime {
        when (args[0]) {
            // 方式1:println を n 回
            "println" -> repeat(n) { println(it) }

            // 方式2:buildString でまとめてから1回だけ出す
            "buildString" -> print(buildString { repeat(n) { appendLine(it) } })

            // 方式3:joinToString でまとめる
            "joinToString" -> println((0 until n).joinToString("\n"))

            // 方式4:PrintWriter(バッファ付き)に書いて最後に flush
            else -> {
                val out = PrintWriter(BufferedWriter(OutputStreamWriter(System.out)))
                repeat(n) { out.println(it) }
                out.flush()
            }
        }
    }
    // 計測結果は標準エラーに出す(標準出力はファイルへ向けるため)
    System.err.println("${args[0]}: ${time.inWholeMilliseconds} ms")
}
kotlinc outbench.kt -include-runtime -d outbench.jar
java -jar outbench.jar println > /dev/null
java -jar outbench.jar buildString > /dev/null
java -jar outbench.jar joinToString > /dev/null
java -jar outbench.jar printWriter > /dev/null
方式 1回目 2回目 3回目
println を100万回 3046 ms 3280 ms 3365 ms
buildString でまとめて print 318 ms 384 ms 568 ms
joinToString("\n") でまとめて println 532 ms 765 ms 672 ms
PrintWriter(バッファ付き)+ flush 404 ms 523 ms 390 ms

println の繰り返しに比べて、まとめ書きは4〜10倍ほど速くなりました。 println は呼ぶたびに出力先へ書き出すため、回数が多いほど差が開きます。

状況 書き方
リストを1行ずつ出す println(list.joinToString("\n"))
空白区切りで1行に出す println(list.joinToString(" "))
ループの中で条件ごとに出す buildString { ... appendLine(x) ... } を最後に print
出力がとても多い PrintWriter に println して、最後に flush()

PrintWriter を使うときは、最後の flush() を忘れると出力がバッファに残ったまま終わります。 out.use { ... } で囲むか、main の最後に flush() を置きます。

よくある質問

Q. readLine() と readln() はどちらを使えばよいですか? A. Kotlin 1.6 以降は readln() と readlnOrNull() が用意されています。readLine()!! は readln()、readLine() は readlnOrNull() に置き換えられます。Kotlin 2.4 のコンパイラも同じ置き換えを案内します。

Q. split(" ") と split(Regex("\s+")) はどう違いますか? A. split(" ") は空白1個ごとに分けるので、空白が2つ続くと間に空文字が入ります。Regex("\\s+") は連続する空白やタブをまとめて1つの区切りとして扱います。先頭や末尾の空白は trim() で落としておきます。

Q. 数を Int と Long のどちらで読めばよいですか? A. 値が約±21億に収まるなら Int、超える可能性があるなら Long です。入力は Int に収まっても、合計や積が超えることがあるので、足し合わせる変数は Long にしておくと安心です。

Q. IDE や手元で入力を試すにはどうすればよいですか? A. 入力をファイルに保存し、java -jar a.jar < input.txt のようにリダイレクトで渡すと、同じ入力で何度でも試せます。短い入力は printf '3\n1 2 3\n' | java -jar a.jar のようにパイプでも渡せます。

まとめ

  • 基本は readln()。数値は toInt() / toLong()、複数の値は split(" ").map { it.toInt() } で読みます。
  • N 行は List(n) { readln() ... }、個数不明は readlnOrNull() が null を返すまで読みます。
  • 空行・余分な空白は NumberFormatException: For input string: "" になります。trim() と split(Regex("\\s+")) で対応できます。
  • 行が無いのに readln() を呼ぶと ReadAfterEOFException(RuntimeException の一種)。readlnOrNull() に替えます。
  • 100万行の入力では、readln+split が約2.8〜3.1秒、StringTokenizer が約0.7〜1.7秒、バイト読み取りが約0.3〜0.4秒でした。
  • 出力は println を繰り返すより、buildString・joinToString・PrintWriter でまとめると4〜10倍ほど速くなりました。

参考・出典

  • Kotlin 標準ライブラリ API リファレンス kotlin.io(readln・readlnOrNull・readLine・println の定義、対応バージョン): https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.io/

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Kotlin 標準入力の書き方 readln・split と高速入力」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?