CLOVER🍀

That was when it all began.

いろんな言語で、文字コードを指定してファイル読み込み

ふと、書いてみたくなりまして。だいたいスクリプト系の言語で、よく忘れてしまうのが書いた動機です…。

記載パターンは、以下の2通り

  • ファイルの中身を全て読み込んで、1つの文字列として変数contentsに格納
  • ファイルの中身を全て読み込んで、改行無しの1行を要素としたリストの変数linesに格納

あと、前提は以下の通り

  • 読み込むファイルの名前は「input.txt」で、中身のエンコーディングは「UTF-8」とする
  • ファイルを開く時に、エンコーディングは「UTF-8」と明示的に指定する
  • 実行効率は求めない
  • ファイルの中身がメモリに乗り切らないなんて考えない
  • 言語の標準ライブラリだけで実装する

要は、簡単なスクリプト的にファイルを読み込みたい時に使うことを想定してます。

最後に、読み込んだ内容を出力します。
では、いってみましょう。

Java

いっつも面倒だと思っていたのですが、JDK 7からだいぶ楽になりましたね。
まずは雛形コード。

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

public class ReadFile {
    public static void main(String[] args) {
        try {
            // code here...
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

コメント「// code here...」の部分を埋めていきます。

まずは、ファイルの中身を1つの文字列として取得する方から。

            String contents = new String(Files.readAllBytes(Paths.get("input.txt")), StandardCharsets.UTF_8);
            // String contents = new String(Files.readAllBytes(Paths.get("input.txt")), "UTF-8");  // <= もしくは、こちら
            System.out.println(contents);

java.nio.file.Files#readAllBytesで一括で読んで、その後Stringに変換。よって、効率は良くないです…。
JDK 7から、java.nio.charset.StandardCharsetsってクラスが入ったんですね。

続いて、ファイルを行単位のList形式で読む方。

            List<String> lines = Files.readAllLines(Paths.get("input.txt"), StandardCharsets.UTF_8);
            for (String line : lines) {
                System.out.println(line);
            }

java.nio.file.Files#readAllLinesで、Listで一気に読めます。
クローズ処理も不要で、随分簡単になったなぁ〜と。

Groovy

JDK 7とか関係なく、楽。

文字列として取得。

def contents = new File('input.txt').getText('UTF-8')
println(contents)

Listとして取得。

def lines = new File('input.txt').readLines('UTF-8')
lines.each { println(it) }

GDK、楽。

Scala

こちらは、ちょっと何パターンか遊んでみました。複数書いた都合上、文字列、Listでの取得を続けて書きます。

まずは、JDK 7が使える環境の場合。

import scala.collection.JavaConverters._
import java.nio.charset.StandardCharsets
import java.nio.file.{Files, Paths}

// 文字列として取得
val contents = new String(Files.readAllBytes(Paths.get("input.txt")), StandardCharsets.UTF_8)
println(contents)

// Listとして取得
val lines = Files.readAllLines(Paths.get("input.txt"), StandardCharsets.UTF_8).asScala.toList
lines foreach println

続いて、Iterator#continuallyで遊んでみました。

// 文字列として取得
import java.io.{BufferedReader, FileInputStream, InputStreamReader}
import java.nio.charset.StandardCharsets
var fis: FileInputStream = null
var isr: InputStreamReader = null
var reader: BufferedReader = null
try {
  fis = new FileInputStream("input.txt")
  isr = new InputStreamReader(fis, StandardCharsets.UTF_8)
  reader = new BufferedReader(isr)
  val text = Iterator.continually(reader.read()).takeWhile(_ != -1).map(_.asInstanceOf[Char]).mkString
  println(text)
} finally {
  if (reader != null) {
    reader.close()
  }

  if (isr != null) {
    isr.close()
  }

  if (fis != null) {
    fis.close()
  }
}

// Listとして取得
import java.io.{BufferedReader, FileInputStream, InputStreamReader}
import java.nio.charset.StandardCharsets
var fis: FileInputStream = null
var isr: InputStreamReader = null
var reader: BufferedReader = null
try {
  fis = new FileInputStream("input.txt")
  isr = new InputStreamReader(fis, StandardCharsets.UTF_8)
  reader = new BufferedReader(isr)
  val lines = Iterator.continually(reader.readLine()).takeWhile(_ != null).toList
  lines foreach println
} finally {
  if (reader != null) {
    reader.close()
  }
  if (isr != null) {
    isr.close()
  }
  if (fis != null) {
    fis.close()
  }
}

急にScalaJavaっぽくなりましたね…。

最後に、評判の悪い?scala.io.Sourceで。

// 文字列として取得
import scala.io.Source

val source = Source.fromFile("input.txt", "UTF-8")
try {
  val contents = source.mkString
  println(contents)
} finally {
  source.close()
}

// Listとして取得
import scala.io.Source

val source = Source.fromFile("input.txt", "UTF-8")
try {
  val lines = source.getLines().toList
  lines foreach println
} finally {
  source.close()
}

普通に読むだけなら、まあいい?でも、書き込みはできないんですよねー。

Perl

実は、今回1番ちゃんとやっておきたかった言語。One Linerばっかり使ってるんで、普通にファイル読み込みしようと思うとコロっと忘れてることが多いんですよね…。

文字列として、一気に読む場合。

#!/usr/bin/perl

use strict;
use warnings;

use Encode 'encode';

my $input_file = 'input.txt';
open my $file, '<:encoding(utf8)', $input_file or die qq{Can't open file: "$input_file"\n};
local $/ = undef;
my $contents = <$file>;
print encode('utf8', $contents);
close $file;

ポイントは、

local $/ = undef;

らしいです。

リスト(配列)で読む場合。

#!/usr/bin/perl

use strict;
use warnings;

use Encode 'encode';

my $input_file = 'input.txt';
open my $file, '<:encoding(utf8)', $input_file or die qq{Can't open file: "$input_file"\n};
my @lines = split /\r?\n/, do { local $/ = undef; <$file> };

foreach my $line (@lines) {
    print encode('utf8', $line), "\n";
}
close $file;

Perlって、きちんとエンコーディング指定して文字列作っちゃうと、printで出力する時にencode関数がいるんですねー。

ホント、この辺りは常識感がないです…。

なんとなく、普通は改行込みで

my @lines = <$fh>;

とか

while (<$fh>) {
  # ...
}

とかすることの方が多いと思いますが…。

Python

文字列で、一気に取得する場合。

#!/usr/bin/python
# -*- coding: utf-8 -*-

import codecs

f = codecs.open("input.txt", "r", "utf-8")
try:
    contents = f.read()
    print contents
finally:
    f.close()

リストで読む場合。

#!/usr/bin/python
# coding: utf-8

import codecs

f = codecs.open("input.txt", "r", "utf-8")
try:
    lines = [line.rstrip("\r\n") for line in f]
    for line in lines:
        print line
finally:
    f.close()

Pythonの場合、readlineとかで改行が除去されないので、自分で取ってます。

やっぱり、普段あんまり使わないPerlPythonをよく忘れますねぇ。Perlは本当に覚えてませんでした…。