各言語でファイル入出力+文字エンコーディング変換
各言語でファイル入出力と文字エンコーディング変換を書いてみたメモ。
やってる途中で、別々のエントリに分けた方が良かったかもと思ったりもしたが、例えばJavaなんかは内部的には「文字」はUTF-8だったりして入出力と文字エンコーディング変換が深くかかわっていたりするので、まぁいいかということで。
要件は以下の通り。
環境
手元にあるものということで、環境は以下のものに限定する。
- CentOS 7
Java
java.nio.charsetパッケージのCharsetDecoder / CharsetEncoderの存在を今更知ったので、2パターン書いてみた。
- パターン1
- パターン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()); } } /* flush()するためのダミー処理 */ 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()); /* flush() */ 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言語で書いたプログラムのうち、ファイル入出力の部分を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; }
PHP
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);
Python 2
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 3
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と何ら変わりはない。
Ruby
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()
Perl
パターン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」することも忘れずに。
bash
#! /bin/bash 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"
各言語の標準エラー出力
そういえば今まで意識しなかったな、ということで、各言語の標準エラー出力を使ってみたメモ。
環境
手元にあるものということで、環境は以下のものに限定する。
- CentOS 7
- Java (openjdk version "1.8.0_151")
- C (gcc (GCC) 4.8.5)
-std=gnu11
でコンパイル
- C++ (g++ (GCC) 4.8.5)
-std=gnu++1y
でコンパイル
- PHP (PHP 5.4.16 (cli))
- Python 2 (Python 2.7.5)
- Python 3 (Python 3.6.3)
- ソースからビルドしたもの
- Ruby (ruby 2.0.0p648)
- Perl (v5.16.3)
- Go (go version go1.8.3 linux/amd64)
- bash (4.2.46(1)-release)
- Awk (GNU Awk 4.0.2)
Java
import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { byte[] buf = new byte[1024]; int len; while ((len = System.in.read(buf)) > 0) { System.err.write(buf, 0, len); } } }
System.in
が標準入力、System.out
が標準出力、System.err
が標準エラー出力を表す。
ちなみに、System.setIn(InputStream)
、System.setOut(PrintStream)
、System.setErr(PrintStream)
というのがあるので、例えば標準出力や標準エラー出力をファイルに書き出すように切り替えるとかも可能。
C
#include <stdio.h> int main(int argc, char** argv) { char buf[1024]; while (fgets(buf, sizeof(buf), stdin)) { fputs(buf, stderr); } return 0; }
FILE*型だと、stdin
が標準入力、stdout
が標準出力、stderr
が標準エラー出力。
ちなみに、int型のファイルディスクリプタ(fd)だと、0
が標準入力、1
が標準出力、2
が標準エラー出力に関連付いている。
unistd.h
を見ると、以下のように書いてある。
/* Standard file descriptors. */ #define STDIN_FILENO 0 /* Standard input. */ #define STDOUT_FILENO 1 /* Standard output. */ #define STDERR_FILENO 2 /* Standard error output. */
C++
#include <iostream> using namespace std; int main(int argc, char** argv) { char buf[1024]; while (cin.getline(buf, sizeof(buf))) { cerr << buf << endl; } return EXIT_SUCCESS; }
std::cin
が標準入力、std::cout
が標準出力、std::cerr
とstd::clog
が標準エラー出力にそれぞれ関連付いている。
std::cerr
とstd::clog
の違いは、前者がバッファリングされないのに対して、後者がバッファリングされるというもの。
iostream
を見ると、以下のように書いてある。
namespace std _GLIBCXX_VISIBILITY(default) { // (中略) extern istream cin; /// Linked to standard input extern ostream cout; /// Linked to standard output extern ostream cerr; /// Linked to standard error (unbuffered) extern ostream clog; /// Linked to standard error (buffered)
std::clog
の存在は、このヘッダファイルを見て初めて知った。
PHP
<?php $str = file_get_contents('php://stdin'); file_put_contents('php://stderr', $str);
PHPはどちらかというとWebアプリ開発に使われる言語ということで、特に意識せずにprintとかすると標準出力に書き出されるので、標準入出力を意識することは少ないかもしれない。
'php://stdin'
が標準入力、'php://stdout'
が標準出力、'php://stderr'
が標準エラー出力を表すので、これをfile_get_contents / file_put_contentsやfopen等にファイル名と同様に指定してやればよい。
Python 2
import sys while True: line = sys.stdin.readline() if line == '': break sys.stderr.write(line)
sys.stdin
が標準入力、sys.stdout
が標準出力、sys.stderr
が標準エラー出力。
Python 3
import sys while True: line = sys.stdin.buffer.readline() if line == b'': break sys.stderr.buffer.write(line)
sys.stdin
が標準入力、sys.stdout
が標準出力、sys.stderr
が標準エラー出力。
Ruby
while line = STDIN.gets STDERR.puts line end
STDIN
が標準入力、STDOUT
が標準出力、STDERR
が標準エラー出力。
Perl
while ((my $line = <STDIN>)) { print STDERR $line; }
STDIN
が標準入力、STDOUT
が標準出力、STDERR
が標準エラー出力。
Go
package main import ( "bufio" "io" "os" ) func main() { stdin := bufio.NewReader(os.Stdin) stderr := bufio.NewWriter(os.Stderr) for { ch, err := stdin.ReadByte() if err == io.EOF { break } stderr.WriteByte(ch) } stderr.Flush() }
os.Stdin
が標準入力、os.Stdout
が標準出力、os.Stderr
が標準エラー出力。
bash
#! /bin/bash cat <&0 >&2
0
が標準入力、1
が標準出力、2
が標準エラー出力を表すので、標準エラー出力に出したい場合は、>&2
のように書いてリダイレクトしてやればよい。
/dev/stdin
、/dev/stdout
、/dev/stderr
を使う手もあるので、>> /dev/stderr
のようにリダイレクトしてもよい。
Awk
{ print >> "/dev/stderr"; }
"/dev/stdin"
が標準入力、"/dev/stdout"
が標準出力、"/dev/stderr"
が標準エラー出力。
まぁ、標準入力や標準出力を明示することはほとんどないけど。
DateTimeクラスのdiffメソッドの罠
突然ですが、以下のコードの出力結果はどうなると思いますか?ちなみに「invert」というのは、結果が負の場合に「1」それ以外の場合に「0」になるプロパティ。
<?php date_default_timezone_set("Asia/Tokyo"); $dt1 = new DateTime("2017-12-01"); $dt2 = new DateTime("2017-11-30"); $diff1 = $dt1->diff($dt2); $diff2 = date_diff($dt1, $dt2); var_dump($diff1->invert, $diff2->invert);
$dt1
の方が未来なので、直観的にはint(0)
になるだろうと思うところだが・・
ドキュメントは以下だけど・・・
明記されていないが、$dt2 - $dt1
の結果になる。すなわち、負になるので、int(1)
が出力される。
これは罠過ぎるだろう!!!
なので、DateTimeオブジェクト同士の日時の前後を比較したいだけなら、(PHP 5.2.2以降になるが)比較演算子で比較するようにした方が安全かも。
<?php date_default_timezone_set("Asia/Tokyo"); $dt1 = new DateTime("2017-12-01"); $dt2 = new DateTime("2017-11-30"); var_dump($dt1 == $dt2); var_dump($dt1 < $dt2); var_dump($dt1 <= $dt2); var_dump($dt1 > $dt2); var_dump($dt1 >= $dt2);
いやそれリークしますからっ!
以下のようなプログラムを見つけて、思わず机を叩き割ろうかと思いましたよ、えぇ‥
Reader reader = null; try { for (int i = 0; i < files.length; ++i) { reader = new BufferedReader(new InputStreamReader(new FileInputStream(files[i]))); // 以下、いろいろ処理(ただしreaderのclose()以外) } } finally { if (reader != null) { try { reader.close(); } catch (Exception e) {} } }
いや、これだと、最後に開いたファイルのストリームしか閉じないんですけど‥
バッチプログラムで、長くても数分で終了するプログラムだからまだ影響が少なかったが、Webアプリでこれをやったら確実にアウトですよ、アウト‥
参考
各言語でコマンドライン引数を扱う
そういえばやってなかったなということで、各言語でコマンドライン引数を扱うプログラムを書いてみたメモ。
要件は以下の通り。
- コマンドライン引数として、3個以上の文字列を与える
- 標準出力に、以下を順に出力
- 引数として与えられた文字列を改行区切りで出力
- 引数の個数を出力
- 引数の3番目の文字列を出力
環境
手元にあるものということで、環境は以下のものに限定する。
- CentOS 7
実行例
[各言語でのコマンド実行] a b c d e
出力例
a b c d e 5 c
Java
public class Main { public static void main(String[] args) { for (String s : args) { System.out.println(s); } System.out.println(args.length); System.out.println(args[2]); } }
C
#include <stdio.h> int main(int argc, char** argv) { for (int i = 1; i < argc; ++i) { printf("%s\n", argv[i]); } printf("%d\n", argc - 1); printf("%s\n", argv[3]); return 0; }
C++
#include <iostream> using namespace std; int main(int argc, char** argv) { for (int i = 1; i < argc; ++i) { cout << argv[i] << endl; } cout << (argc - 1) << endl; cout << argv[3] << endl; return EXIT_SUCCESS; }
PHP
<?php for ($i = 1; $i < count($argv); ++$i) { echo $argv[$i] . PHP_EOL; } echo (count($argv) - 1) . PHP_EOL; echo $argv[3] . PHP_EOL;
Python 2
import sys for i in range(1, len(sys.argv)): print sys.argv[i] print len(sys.argv) - 1 print sys.argv[3]
Python 3
import sys for i in range(1, len(sys.argv)): print(sys.argv[i]) print(len(sys.argv) - 1) print(sys.argv[3])
Ruby
for a in ARGV print a,"\n" end print ARGV.length,"\n" print ARGV[2],"\n"
Perl
for (my $i = 0; $i < @ARGV; ++$i) { print $ARGV[$i],"\n"; } my $argc = @ARGV; print $argc,"\n"; print $ARGV[2],"\n";
Go
package main import ( "fmt" "os" ) func main() { for i := 1; i < len(os.Args); i += 1 { fmt.Println(os.Args[i]) } fmt.Println(len(os.Args) - 1) fmt.Println(os.Args[3]); }
bash
#! /bin/bash for c in "$@" ; do echo ${c} done echo $# echo $3
まとめ
それぞれの言語ごとの仕様を表にまとめてみる。
言語 | 変数名 | 最初の引数の添え字 | 個数 |
---|---|---|---|
Java (*1) | args | 0 | args.length |
C (*1) | argv | 1 | argc - 1 |
C++ (*1) | argv | 1 | argc - 1 |
PHP | $argv | 1 | count($argv) - 1 |
Python 2/3 | sys.argv | 1 | len(sys.argv) - 1 |
Ruby | ARGV | 0 | ARGV.length |
Perl | $ARGV | 0 | @ARGV (*3) |
Go | os.Args | 1 | len(os.Args) - 1 |
bash | $n または ${n} (*2) | 1 | $# |
- (*1)変数名はmainメソッド/関数の引数の宣言による。
- (*2)「n」は正の整数。
- (*3)文脈によっては、「my $argc = @ARGV」などとしてやらないと、個数でなく引数を結合した文字列が得られてしまう。
文字列のマッチング方法によるパフォーマンスの違い
Javaで文字列のパターンマッチをしようと思ったら、以下の3通りに書ける。
String str = "Hello World"; if (str.matches("H.*W")) { ... }
String str = "Hello World"; if (java.util.regex.Pattern.matches("H.*W", str)) { ... }
String str = "Hello World"; java.util.regex.Pattern p = java.util.regex.Pattern.compile("H.*W"); java.util.regex.Matcher m = m.matcher(str); if (m.matches()) { ... }
まぁ、1番目は内部で2番目の処理を呼び出しているだけだが。
で、3番目のように正規表現のコンパイルを事前に行っておく形とそうでない形とでパフォーマンスにどれだけの差が出るのだろうかとふと思ったので計ってみた。
計測に使用したプログラム
import java.util.Scanner; import java.util.regex.Matcher; import java.util.regex.Pattern; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); String str = sc.nextLine(); String regex = sc.nextLine(); int delta = 100000; int max = 2000000; for (int i = delta; i <= max; i += delta) { System.out.printf("%8d,%8d,%8d,%8d%n", i, test1(i, str, regex), test2(i, str, regex), test3(i, str, regex)); } } private static long test1(int count, String str, String regex) { long S = System.currentTimeMillis(); for (int i = 0; i < count; ++i) { str.matches(regex); } long G = System.currentTimeMillis(); return G - S; } private static long test2(int count, String str, String regex) { long S = System.currentTimeMillis(); for (int i = 0; i < count; ++i) { Pattern.matches(regex, str); } long G = System.currentTimeMillis(); return G - S; } private static long test3(int count, String str, String regex) { long S = System.currentTimeMillis(); Pattern p = Pattern.compile(regex); for (int i = 0; i < count; ++i) { Matcher m = p.matcher(str); m.matches(); } long G = System.currentTimeMillis(); return G - S; } }
入力
正規表現のコンパイルにかかる時間に着目しているので、正規表現の複雑さの違いということで2種類の正規表現を試してみた。
- a:
[a-z]+
- b:
[A-Z][a-z]* [A-Z][a-z]*
計測結果
- 正規表現 a
N | test1-a | test2-a | test3-a |
---|---|---|---|
100000 | 191 | 64 | 20 |
200000 | 172 | 68 | 22 |
300000 | 79 | 64 | 26 |
400000 | 83 | 78 | 21 |
500000 | 100 | 99 | 25 |
600000 | 120 | 121 | 31 |
700000 | 138 | 140 | 36 |
800000 | 154 | 153 | 39 |
900000 | 176 | 177 | 45 |
1000000 | 205 | 197 | 50 |
1100000 | 217 | 217 | 54 |
1200000 | 235 | 239 | 61 |
1300000 | 258 | 259 | 65 |
1400000 | 279 | 274 | 71 |
1500000 | 306 | 292 | 76 |
1600000 | 331 | 326 | 86 |
1700000 | 340 | 348 | 89 |
1800000 | 364 | 362 | 91 |
1900000 | 390 | 383 | 97 |
2000000 | 410 | 392 | 100 |
正規表現 b
N | test1-b | test2-b | test3-b |
---|---|---|---|
100000 | 313 | 92 | 19 |
200000 | 217 | 130 | 46 |
300000 | 164 | 161 | 42 |
400000 | 213 | 217 | 52 |
500000 | 273 | 271 | 66 |
600000 | 325 | 326 | 79 |
700000 | 379 | 389 | 89 |
800000 | 434 | 431 | 104 |
900000 | 482 | 490 | 117 |
1000000 | 540 | 539 | 131 |
1100000 | 584 | 588 | 143 |
1200000 | 646 | 641 | 156 |
1300000 | 694 | 703 | 173 |
1400000 | 745 | 752 | 187 |
1500000 | 808 | 816 | 195 |
1600000 | 883 | 873 | 211 |
1700000 | 932 | 927 | 226 |
1800000 | 992 | 984 | 241 |
1900000 | 1032 | 1020 | 254 |
2000000 | 1083 | 1088 | 271 |
まぁ予想通りだが、毎回コンパイルすることになるtest1、test2のケースは、test3に比べて断然遅いことが分かる。 また、正規表現の複雑さが増すと、コンパイルやマッチングに時間が掛かることも読み取れる。
各言語でメッセージダイジェスト
手元にある各言語で、メッセージダイジェストを出力してみたメモ。
要件は以下の通り。
- 標準入力から、0バイト以上の任意のバイト列が入力される
- 標準出力に、入力に対する以下のハッシュ文字列を改行区切りで出力する
環境
手元にあるものということで、環境は以下のものに限定する。
- CentOS 7
入力
Hello World!
出力
8ddd8be4b179a529afa5f2ffae4b9858 a0b65939670bc2c010f4d5d6a0b3e4e4590fb92b de9d76f0f6a015ab6629138a42835e7b44571995e4abb291c0817261 03ba204e50d126e4674c005e04d82e84c21366780af1f43bd54a37816b6ab340 07f60df0b95043b3a3717638e7776ab76ebaa4fc705ba659063229cf162980c04a9f7496dcda50de6510d40fde3eba8a 830445e86a0cfafac4e1531002356f384847a11a7456fb8ccb81ab36e37bff28f34fa2c5bfdd347e964c5c5df0fc305de6394368219307b2ceeb0ec84b7c2b31
Java
import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class Main { public static void main(String[] args) throws NoSuchAlgorithmException { MessageDigest[] mdAry = { MessageDigest.getInstance("MD5"), MessageDigest.getInstance("SHA-1"), MessageDigest.getInstance("SHA-224"), MessageDigest.getInstance("SHA-256"), MessageDigest.getInstance("SHA-384"), MessageDigest.getInstance("SHA-512"), }; try (InputStream in = new BufferedInputStream(System.in)) { byte[] b = new byte[1024]; int len; while ((len = in.read(b)) > 0) { for (MessageDigest md : mdAry) { md.update(b, 0, len); } } } catch (IOException e) { e.printStackTrace(); } for (MessageDigest md : mdAry) { byte[] digest = md.digest(); for (byte b : digest) { System.out.printf("%02x", b); } System.out.println(); } } }
C
#include <stdio.h> #include <string.h> #include <openssl/md5.h> #include <openssl/sha.h> int main(int argc, char** argv) { MD5_CTX md5_ctx; unsigned char md5[MD5_DIGEST_LENGTH]; SHA_CTX sha1_ctx; unsigned char sha1[SHA_DIGEST_LENGTH]; SHA256_CTX sha224_ctx; unsigned char sha224[SHA224_DIGEST_LENGTH]; SHA256_CTX sha256_ctx; unsigned char sha256[SHA256_DIGEST_LENGTH]; SHA512_CTX sha384_ctx; unsigned char sha384[SHA384_DIGEST_LENGTH]; SHA512_CTX sha512_ctx; unsigned char sha512[SHA512_DIGEST_LENGTH]; if (!MD5_Init(&md5_ctx)) { return 1; } if (!SHA1_Init(&sha1_ctx)) { return 1; } if (!SHA224_Init(&sha224_ctx)) { return 1; } if (!SHA256_Init(&sha256_ctx)) { return 1; } if (!SHA384_Init(&sha384_ctx)) { return 1; } if (!SHA512_Init(&sha512_ctx)) { return 1; } char buf[1024]; int len; while ((len = fread(buf, sizeof(char), sizeof(buf), stdin)) > 0) { if (!MD5_Update(&md5_ctx, buf, len)) { return 1; } if (!SHA1_Update(&sha1_ctx, buf, len)) { return 1; } if (!SHA224_Update(&sha224_ctx, buf, len)) { return 1; } if (!SHA256_Update(&sha256_ctx, buf, len)) { return 1; } if (!SHA384_Update(&sha384_ctx, buf, len)) { return 1; } if (!SHA512_Update(&sha512_ctx, buf, len)) { return 1; } } if (!MD5_Final(md5, &md5_ctx)) { return 1; } if (!SHA1_Final(sha1, &sha1_ctx)) { return 1; } if (!SHA224_Final(sha224, &sha224_ctx)) { return 1; } if (!SHA256_Final(sha256, &sha256_ctx)) { return 1; } if (!SHA384_Final(sha384, &sha384_ctx)) { return 1; } if (!SHA512_Final(sha512, &sha512_ctx)) { return 1; } for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) { fprintf(stdout, "%02x", md5[i]); } fprintf(stdout, "\n"); for (int i = 0; i < SHA_DIGEST_LENGTH; ++i) { fprintf(stdout, "%02x", sha1[i]); } fprintf(stdout, "\n"); for (int i = 0; i < SHA224_DIGEST_LENGTH; ++i) { fprintf(stdout, "%02x", sha224[i]); } fprintf(stdout, "\n"); for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i) { fprintf(stdout, "%02x", sha256[i]); } fprintf(stdout, "\n"); for (int i = 0; i < SHA384_DIGEST_LENGTH; ++i) { fprintf(stdout, "%02x", sha384[i]); } fprintf(stdout, "\n"); for (int i = 0; i < SHA512_DIGEST_LENGTH; ++i) { fprintf(stdout, "%02x", sha512[i]); } fprintf(stdout, "\n"); return 0; }
「-lcrypto」を付けてコンパイルする必要がある。
C++
C言語と大して変わらないけど、一応載せておく。
#include <cstdio> #include <string> #include <openssl/md5.h> #include <openssl/sha.h> int main(int argc, char** argv) { MD5_CTX md5_ctx; unsigned char md5[MD5_DIGEST_LENGTH]; SHA_CTX sha1_ctx; unsigned char sha1[SHA_DIGEST_LENGTH]; SHA256_CTX sha224_ctx; unsigned char sha224[SHA224_DIGEST_LENGTH]; SHA256_CTX sha256_ctx; unsigned char sha256[SHA256_DIGEST_LENGTH]; SHA512_CTX sha384_ctx; unsigned char sha384[SHA384_DIGEST_LENGTH]; SHA512_CTX sha512_ctx; unsigned char sha512[SHA512_DIGEST_LENGTH]; if (!MD5_Init(&md5_ctx)) { return EXIT_FAILURE; } if (!SHA1_Init(&sha1_ctx)) { return EXIT_FAILURE; } if (!SHA224_Init(&sha224_ctx)) { return EXIT_FAILURE; } if (!SHA256_Init(&sha256_ctx)) { return EXIT_FAILURE; } if (!SHA384_Init(&sha384_ctx)) { return EXIT_FAILURE; } if (!SHA512_Init(&sha512_ctx)) { return EXIT_FAILURE; } char buf[1024]; int len; while ((len = fread(buf, sizeof(char), sizeof(buf), stdin)) > 0) { if (!MD5_Update(&md5_ctx, buf, len)) { return EXIT_FAILURE; } if (!SHA1_Update(&sha1_ctx, buf, len)) { return EXIT_FAILURE; } if (!SHA224_Update(&sha224_ctx, buf, len)) { return EXIT_FAILURE; } if (!SHA256_Update(&sha256_ctx, buf, len)) { return EXIT_FAILURE; } if (!SHA384_Update(&sha384_ctx, buf, len)) { return EXIT_FAILURE; } if (!SHA512_Update(&sha512_ctx, buf, len)) { return EXIT_FAILURE; } } if (!MD5_Final(md5, &md5_ctx)) { return EXIT_FAILURE; } if (!SHA1_Final(sha1, &sha1_ctx)) { return EXIT_FAILURE; } if (!SHA224_Final(sha224, &sha224_ctx)) { return EXIT_FAILURE; } if (!SHA256_Final(sha256, &sha256_ctx)) { return EXIT_FAILURE; } if (!SHA384_Final(sha384, &sha384_ctx)) { return EXIT_FAILURE; } if (!SHA512_Final(sha512, &sha512_ctx)) { return EXIT_FAILURE; } for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) { fprintf(stdout, "%02x", md5[i]); } fprintf(stdout, "\n"); for (int i = 0; i < SHA_DIGEST_LENGTH; ++i) { fprintf(stdout, "%02x", sha1[i]); } fprintf(stdout, "\n"); for (int i = 0; i < SHA224_DIGEST_LENGTH; ++i) { fprintf(stdout, "%02x", sha224[i]); } fprintf(stdout, "\n"); for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i) { fprintf(stdout, "%02x", sha256[i]); } fprintf(stdout, "\n"); for (int i = 0; i < SHA384_DIGEST_LENGTH; ++i) { fprintf(stdout, "%02x", sha384[i]); } fprintf(stdout, "\n"); for (int i = 0; i < SHA512_DIGEST_LENGTH; ++i) { fprintf(stdout, "%02x", sha512[i]); } fprintf(stdout, "\n"); return EXIT_SUCCESS; }
「-lcrypto」を付けてコンパイルする必要がある。
PHP
<?php $input = file_get_contents('php://stdin'); printf("%s\n", hash('md5', $input)); printf("%s\n", hash('sha1', $input)); printf("%s\n", hash('sha224', $input)); printf("%s\n", hash('sha256', $input)); printf("%s\n", hash('sha384', $input)); printf("%s\n", hash('sha512', $input));
メモリに載りきらないほどのデータが来たら対応できないのが難点。
Python 2
import sys import hashlib md5 = hashlib.md5() sha1 = hashlib.sha1() sha224 = hashlib.sha224() sha256 = hashlib.sha256() sha384 = hashlib.sha384() sha512 = hashlib.sha512() while True: line = sys.stdin.readline() if line == '': break md5.update(line) sha1.update(line) sha224.update(line) sha256.update(line) sha384.update(line) sha512.update(line) print md5.hexdigest() print sha1.hexdigest() print sha224.hexdigest() print sha256.hexdigest() print sha384.hexdigest() print sha512.hexdigest()
Python 3
import sys import hashlib md5 = hashlib.md5() sha1 = hashlib.sha1() sha224 = hashlib.sha224() sha256 = hashlib.sha256() sha384 = hashlib.sha384() sha512 = hashlib.sha512() while True: line = sys.stdin.buffer.readline() if line == b'': break md5.update(line) sha1.update(line) sha224.update(line) sha256.update(line) sha384.update(line) sha512.update(line) print(md5.hexdigest()) print(sha1.hexdigest()) print(sha224.hexdigest()) print(sha256.hexdigest()) print(sha384.hexdigest()) print(sha512.hexdigest())
Ruby
require 'openssl' mdAry = [ OpenSSL::Digest.new("md5"), OpenSSL::Digest.new("sha1"), OpenSSL::Digest.new("sha224"), OpenSSL::Digest.new("sha256"), OpenSSL::Digest.new("sha384"), OpenSSL::Digest.new("sha512"), ] while line = STDIN.gets for md in mdAry md.update(line) end end for md in mdAry puts md.hexdigest end
Perl
use Digest::MD5; use Digest::SHA; my $md5 = Digest::MD5->new(); my $sha1 = Digest::SHA->new("sha1"); my $sha224 = Digest::SHA->new("sha224"); my $sha256 = Digest::SHA->new("sha256"); my $sha384 = Digest::SHA->new("sha384"); my $sha512 = Digest::SHA->new("sha512"); while (my $line = readline(STDIN)) { $md5->add($line); $sha1->add($line); $sha224->add($line); $sha256->add($line); $sha384->add($line); $sha512->add($line); } print $md5->hexdigest(),"\n"; print $sha1->hexdigest(),"\n"; print $sha224->hexdigest(),"\n"; print $sha256->hexdigest(),"\n"; print $sha384->hexdigest(),"\n"; print $sha512->hexdigest(),"\n";
「yum install perl-Digest-MD5 perl-Digest-SHA」が必要。
Go
package main import ( "fmt" "io" "os" "crypto/md5" "crypto/sha1" "crypto/sha256" "crypto/sha512" ) func main() { aMd5 := md5.New() aSha1 := sha1.New() aSha224 := sha256.New224() aSha256 := sha256.New() aSha384 := sha512.New384() aSha512 := sha512.New() for { buf := make([]byte, 1024) n, err := os.Stdin.Read(buf) if err == io.EOF { break } buf2 := make([]byte, 0); buf2 = append(buf2, buf[0:n]...) aMd5.Write(buf2) aSha1.Write(buf2) aSha224.Write(buf2) aSha256.Write(buf2) aSha384.Write(buf2) aSha512.Write(buf2) } fmt.Printf("%x\n", aMd5.Sum(nil)) fmt.Printf("%x\n", aSha1.Sum(nil)) fmt.Printf("%x\n", aSha224.Sum(nil)) fmt.Printf("%x\n", aSha256.Sum(nil)) fmt.Printf("%x\n", aSha384.Sum(nil)) fmt.Printf("%x\n", aSha512.Sum(nil)) }
- md5 - The Go Programming Language
- sha1 - The Go Programming Language
- sha256 - The Go Programming Language
- sha512 - The Go Programming Language
bash
#! /bin/bash cat | tee \ >(sha512sum | cut -d " " -f 1) \ >(sha384sum | cut -d " " -f 1) \ >(sha256sum | cut -d " " -f 1) \ >(sha224sum | cut -d " " -f 1) \ >(sha1sum | cut -d " " -f 1) \ >(md5sum | cut -d " " -f 1) \ >> /dev/null
バイナリデータのテストケースにも対応するためにteeコマンド+コマンド置換を使ったが、期待する順番通りに出力されないことがあるのが難点。
man md5sum
man sha1sum
man sha224sum
man sha256sum
man sha384sum
man sha512sum