各言語でファイル入出力と文字エンコーディング変換を書いてみたメモ。
やってる途中で、別々のエントリに分けた方が良かったかもと思ったりもしたが、例えばJavaなんかは内部的には「文字」はUTF-8だったりして入出力と文字エンコーディング変換が深くかかわっていたりするので、まぁいいかということで。
要件は以下の通り。
環境
手元にあるものということで、環境は以下のものに限定する。
java.nio.charsetパッケージのCharsetDecoder / CharsetEncoderの存在を今更知ったので、2パターン書いてみた。
- パターン1
- ファイル読み込み時にInputStreamReaderで文字エンコーディング変換
- ファイル書き込み時にOutputStreamWriterで文字エンコーディング変換
- パターン2
- 読み込んだバイト列をCharsetDecoderで文字列に変換
- 文字列をCharsetEncoderでバイト列に変換して書き込み
パターン1
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
public class Main {
public static void main(String[] args) {
if (args.length != 1) {
System.err.println("Usage: java Main dirname");
System.exit(1);
return;
}
File inFile = new File(args[0], "in.txt");
File outFile = new File(args[0], "out.txt");
try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(inFile), "EUC-JP"));
PrintWriter out = new PrintWriter(new OutputStreamWriter(new FileOutputStream(outFile), "Windows-31J"))
) {
char[] buf = new char[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
パターン2
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
public class Main {
public static void main(String[] args) {
if (args.length != 1) {
System.err.println("Usage: java Main dirname");
System.exit(1);
return;
}
File inFile = new File(args[0], "in.txt");
File outFile = new File(args[0], "out.txt");
CharsetDecoder decoder = Charset.forName("EUC-JP").newDecoder();
decoder.reset();
CharsetEncoder encoder = Charset.forName("Windows-31J").newEncoder();
encoder.reset();
try (InputStream in = new BufferedInputStream(new FileInputStream(inFile));
PrintStream out = new PrintStream(new FileOutputStream(outFile))
) {
ByteBuffer inBuf = ByteBuffer.allocate(1024);
ByteBuffer outBuf = ByteBuffer.allocate(1024);
CharBuffer tmpBuf = CharBuffer.allocate(1024);
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf, 0, Math.min(buf.length, inBuf.remaining()))) > 0) {
inBuf.put(buf, 0, len);
inBuf.flip();
tmpBuf.clear();
CoderResult res = decoder.decode(inBuf, tmpBuf, false);
if (res.isUnderflow()) {
inBuf.compact();
tmpBuf.flip();
outBuf.clear();
encoder.encode(tmpBuf, outBuf, false);
outBuf.flip();
outBuf.get(buf, 0, outBuf.limit());
out.write(buf, 0, outBuf.limit());
}
}
inBuf.clear();
inBuf.flip();
tmpBuf.clear();
decoder.decode(inBuf, tmpBuf, true);
tmpBuf.flip();
outBuf.clear();
encoder.encode(tmpBuf, outBuf, true);
outBuf.flip();
outBuf.get(buf, 0, outBuf.limit());
out.write(buf, 0, outBuf.limit());
tmpBuf.clear();
decoder.flush(tmpBuf);
tmpBuf.flip();
outBuf.clear();
encoder.flush(outBuf);
outBuf.flip();
outBuf.get(buf, 0, outBuf.limit());
out.write(buf, 0, outBuf.limit());
} catch (IOException e) {
e.printStackTrace();
}
}
}
なんか、CharsetDecoder / CharsetEncoderの挙動を理解するのにすごく時間が掛かった、というか、そもそもByteBuffer / CharBufferの挙動もよく分からんかった。今でも、上記プログラムでほんとに正しいのか全く自信が無い‥
C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iconv.h>
#include <limits.h>
#include <errno.h>
#define BUF_SIZE 1024
int main(int argc, char** argv) {
if (argc != 2) {
fprintf(stderr, "Usage: %s dirname\n", argv[0]);
return 1;
}
char inFile[PATH_MAX];
sprintf(inFile, "%s/in.txt", argv[1]);
char outFile[PATH_MAX];
sprintf(outFile, "%s/out.txt", argv[1]);
FILE* inFp = fopen(inFile, "r");
if (!inFp) {
perror(inFile);
return 1;
}
FILE* outFp = fopen(outFile, "w");
if (!outFp) {
perror(outFile);
fclose(inFp);
return 1;
}
iconv_t iconvHandler = iconv_open("CP932", "EUC-JP");
char inBuf[BUF_SIZE];
size_t inBufLeft = 0;
char outBuf[BUF_SIZE];
int len;
while ((len = fread(inBuf + inBufLeft, sizeof(char), sizeof(inBuf) - inBufLeft, inFp)) + inBufLeft > 0) {
inBufLeft += len;
char* inPtr = inBuf;
char* outPtr = outBuf;
size_t outBufLeft = sizeof(outBuf);
int rc = iconv(iconvHandler, &inPtr, &inBufLeft, &outPtr, &outBufLeft);
if (rc == -1 && (errno == EILSEQ || errno == E2BIG)) {
perror("iconv");
break;
}
fwrite(outBuf, sizeof(char), sizeof(outBuf) - outBufLeft, outFp);
if (inBufLeft > 0) {
strncpy(inBuf, inPtr, inBufLeft);
}
}
iconv_close(iconvHandler);
fclose(inFp);
fclose(outFp);
return 0;
}
iconvの挙動を理解するのにすごい苦労した。というか、ネット上に転がっていたサンプルをいくつか試してみたが、入力データにちょっと細工したりするとすぐにテストケースでNGが出たりして使えないということになり、結局manページを熟読して理解したという・・・
C言語で書いたプログラムのうち、ファイル入出力の部分をC++版にしてみたもの。
#include <fstream>
#include <iostream>
#include <string>
#include <cstring>
#include <iconv.h>
#include <limits.h>
#include <errno.h>
#define BUF_SIZE 1024
using namespace std;
int main(int argc, char** argv) {
if (argc != 2) {
fprintf(stderr, "Usage: %s dirname\n", argv[0]);
return EXIT_FAILURE;
}
char inFile[PATH_MAX];
sprintf(inFile, "%s/in.txt", argv[1]);
char outFile[PATH_MAX];
sprintf(outFile, "%s/out.txt", argv[1]);
ifstream inFs(inFile, ios::binary);
if (inFs.fail()) {
perror(inFile);
return EXIT_FAILURE;
}
ofstream outFs(outFile, ios::binary);
if (outFs.fail()) {
perror(outFile);
return EXIT_FAILURE;
}
iconv_t iconvHandler = iconv_open("CP932", "EUC-JP");
char inBuf[BUF_SIZE / 2];
size_t inBufLeft = 0;
char outBuf[BUF_SIZE * 2];
streamsize len;
while ((len = inFs.readsome(inBuf + inBufLeft, sizeof(inBuf) - inBufLeft)) + inBufLeft > 0) {
inBufLeft += len;
char* inPtr = inBuf;
char* outPtr = outBuf;
size_t outBufLeft = sizeof(outBuf);
int rc = iconv(iconvHandler, &inPtr, &inBufLeft, &outPtr, &outBufLeft);
if (rc == -1 && (errno == EILSEQ || errno == E2BIG)) {
perror("iconv");
break;
}
outFs.write(outBuf, sizeof(outBuf) - outBufLeft);
if (inBufLeft > 0) {
strncpy(inBuf, inPtr, inBufLeft);
}
}
iconv_close(iconvHandler);
return EXIT_SUCCESS;
}
2パターン思いついたので、それぞれ書いてみた。
- パターン1
- ファイルI/Oは、fopen / fgets / fputs / fclose
- 文字エンコーディング変換は、mb_convert_encoding
- パターン2
- ファイルI/Oは、file_get_contents / file_put_contents
- 文字エンコーディング変換は、iconv
パターン1
<?php
if (count($argv) < 2) {
file_put_contents('php://stderr', "Usage: php {$argv[0]} dirname" . PHP_EOL);
exit(1);
}
$dir = $argv[1];
if (!file_exists($dir) || !is_dir($dir)) {
file_put_contents('php://stderr', "{$dir}: No such directory." . PHP_EOL);
exit(1);
}
$inFile = "{$dir}/in.txt";
$outFile = "{$dir}/out.txt";
$inFp = fopen($inFile, 'rb');
if (!$inFp) {
file_put_contents('php://stderr', "{$inFile}: Cannot open file." . PHP_EOL);
exit(1);
}
$outFp = fopen($outFile, 'wb');
if (!$outFp) {
file_put_contents('php://stderr', "{$outFile}: Cannot open file." . PHP_EOL);
fclose($inFp);
exit(1);
}
while (($line = fgets($inFp))) {
$line = mb_convert_encoding($line, 'SJIS-win', 'eucJP-win');
fputs($outFp, $line);
}
fclose($inFp);
fclose($outFp);
パターン2
<?php
if (count($argv) < 2) {
file_put_contents('php://stderr', "Usage: php {$argv[0]} dirname" . PHP_EOL);
exit(1);
}
$dir = $argv[1];
if (!is_dir($dir)) {
file_put_contents('php://stderr', "{$dir}: No such directory." . PHP_EOL);
exit(1);
}
$inFile = "{$dir}/in.txt";
$outFile = "{$dir}/out.txt";
if (!file_exists($inFile)) {
file_put_contents('php://stderr', "{$inFile}: No such file." . PHP_EOL);
exit(1);
}
$str = file_get_contents($inFile);
$str = iconv('eucJP-win', 'SJIS-win', $str);
file_put_contents($outFile, $str);
import sys
import os
import codecs
if len(sys.argv) < 2:
sys.stderr.write("Usage: " + sys.argv[0] + " dirname\n")
exit(1)
dirname = sys.argv[1]
inFile = dirname + "/in.txt"
outFile = dirname + "/out.txt"
if not os.path.isfile(inFile):
sys.stderr.write(inFile + ": No such file\n")
exit(1)
inFp = codecs.open(inFile, 'rb', 'EUC-JP')
outFp = codecs.open(outFile, 'wb', 'CP932')
for line in inFp:
outFp.write(line)
inFp.close()
outFp.close()
import sys
import os
import codecs
if len(sys.argv) < 2:
sys.stderr.write("Usage: " + sys.argv[0] + " dirname\n")
exit(1)
dirname = sys.argv[1]
inFile = dirname + "/in.txt"
outFile = dirname + "/out.txt"
if not os.path.isfile(inFile):
sys.stderr.write(inFile + ": No such file\n")
exit(1)
inFp = codecs.open(inFile, 'rb', 'EUC-JP')
outFp = codecs.open(outFile, 'wb', 'CP932')
for line in inFp:
outFp.write(line)
inFp.close()
outFp.close()
Python 2と何ら変わりはない。
if ARGV.length != 1
STDERR.puts('Usage: ' + __FILE__ + ' dirname')
exit 1
end
dir = ARGV[0]
inFile = dir + '/in.txt'
outFile = dir + '/out.txt'
if !File.file?(inFile)
STDERR.puts(inFile + ': No such file')
exit 1
end
inFp = File.open(inFile, mode = 'rb')
outFp = File.open(outFile, mode = 'wb')
inFp.each_line{|line|
line.encode!('CP932', 'EUC-JP')
outFp.puts(line)
}
inFp.close()
outFp.close()
パターン1
if (@ARGV < 1) {
die("Usage: " . __FILE__ . " dirname");
}
my $dir = $ARGV[0];
my $inFile = $dir . "/in.txt";
my $outFile = $dir . "/out.txt";
open(inFp, "<:encoding(EUC-JP)", $inFile) or die($inFile . ": $!");
open(outFp, ">:encoding(CP932)", $outFile) or die($outFile . ": $!");
while (my $line = <inFp>) {
print outFp $line;
}
close(inFp);
close(outFp);
パターン2
use Encode;
if (@ARGV < 1) {
die("Usage: " . __FILE__ . " dirname");
}
my $dir = $ARGV[0];
my $inFile = $dir . "/in.txt";
my $outFile = $dir . "/out.txt";
open(inFp, "<", $inFile) or die($inFile . ": $!");
open(outFp, ">", $outFile) or die($outFile . ": $!");
while (my $line = <inFp>) {
$line = encode('CP932', decode('EUC-JP', $line));
print outFp $line;
}
close(inFp);
close(outFp);
Go
package main
import (
"fmt"
"os"
"io"
"bufio"
"golang.org/x/text/encoding/japanese"
"golang.org/x/text/transform"
)
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "Usage: " + os.Args[0] + " dirname")
os.Exit(1)
}
dir := os.Args[1]
inFile := dir + "/in.txt"
outFile := dir + "/out.txt"
inFp, err := os.Open(inFile)
if err != nil {
panic(err)
}
outFp, err := os.Create(outFile)
if err != nil {
inFp.Close()
panic(err)
}
in := bufio.NewReader(transform.NewReader(inFp, japanese.EUCJP.NewDecoder()))
out := bufio.NewWriter(transform.NewWriter(outFp, japanese.ShiftJIS.NewEncoder()))
b := make([]byte, 1024)
for {
n, err := in.Read(b)
if err == io.EOF {
break
} else if err != nil {
inFp.Close()
outFp.Close()
panic(err)
}
out.Write(b[0:n])
}
out.Flush()
inFp.Close()
outFp.Close()
}
これをやるには「GOPATH=$(pwd) go get golang.org/x/text/encoding/japanese」しておく必要がある。更に実行時に「GOPATH=$(pwd) go run Main.go」することも忘れずに。
dir="${1}"
if [ -z "${dir}" ]; then
echo "Usage: $0 dirname" >> /dev/stderr
exit 1
fi
if [ ! -d "${dir}" ]; then
echo "${dir}: No such directory." >> /dev/stderr
exit 1
fi
iconv -f EUC-JP -t CP932 < "${dir}/in.txt" > "${dir}/out.txt"