LoginSignup
1
0

More than 5 years have passed since last update.

Nimで文字コード変換

Posted at

はじめに

Nimで文字コード変換を変換する方法を記載します。
内容自体はメモレベルです。

環境

  • Ubuntu18.10
  • Nim 0.19.4

シンプルに文字コードを変換する

Linux環境では単純にCP932のファイルを読み取ると文字化けします。

echo readFile("cp932.txt")

単純にechoしても文字化けしてしまい、端末から文字を読み取れません。

UTF-8以外のデータを読み取ったときは標準モジュールのencodingsモジュールを使用して文字コードをUTF-8に変換します。

import encodings

# 文字化けする
echo readFile("cp932.txt")

# 文字化けしない
echo readFile("cp932.txt").`$`.convert(srcEncoding="CP932", destEncoding="UTF-8")

これで端末上の出力でも文字化けしていないことが確認できます。

ファイルに書き込む場合は、このまま書き込んでしまえば良いです。

import encodings

var utf8data = readFile("cp932.txt").`$`.convert(srcEncoding="CP932", destEncoding="UTF-8")
writeFile("utf-8.txt", utf8data)

これでファイルもUTF-8として書き込まれます。

巨大ファイルを扱う

単純にreadFileで開いてエンコードする場合だと
ファイルサイズが巨大だった場合に処理しきれない可能性が有ります。
その時はstreamsモジュールを使います。

import encodings, streams

var
  conv = encodings.open(srcEncoding="CP932", destEncoding="UTF-8")
  strm = newFileStream("cp932.txt", fmRead)
  outStrm = newFileStream("utf-8.txt", fmWrite)
  line: string

while strm.readLine(line):
  outStrm.writeLine conv.convert(line)

outStrm.close
strm.close
conv.close

これで1行ずつテキストを読み取り、都度文字コードを変換してファイル書き込みできるようになりました。

しかしながら、1行あたりのテキスト量が少なくて
行数が非常に多いような巨大ファイルの場合にめちゃくちゃ時間がかかってしまいます。
(この記事を書くにあたって、そのようなダミーデータを作成してしまい速度がでない問題に直面しました)

その場合は下記のように別のreadStrなど別のプロシージャを使うことで速度を改善できる場合が有ります。
ただしその方法には問題が有ります(後述)
readFile、readLine、readStrの処理速度を比較するコードを用意しました。

import encodings, streams, times
from strformat import `&`

# 巨大ファイルをreadFileで読み取る例
block:
  let
    startTime = cpuTime()
    utf8data = readFile("cp932big.txt").`$`.convert(srcEncoding="CP932", destEncoding="UTF-8")
  writeFile("utf-8big.txt", utf8data)

  echo &"system readFile example: {cpuTime()-startTime} sec"

# 巨大ファイルをreadLineで読み取る例
block:
  let
    startTime = cpuTime()
  var
    conv = encodings.open(srcEncoding="CP932", destEncoding="UTF-8")
    strm = newFileStream("cp932big.txt", fmRead)
    outStrm = newFileStream("utf-8big.txt", fmWrite)
    line: string

  while strm.readLine(line):
    outStrm.writeLine conv.convert(line)

  outStrm.close
  strm.close
  conv.close

  echo &"streams readLine example: {cpuTime()-startTime} sec"

# 巨大ファイルをreadStrで読み取る例
block:
  let
    startTime = cpuTime()
  var
    conv = encodings.open(srcEncoding="CP932", destEncoding="UTF-8")
    strm = newFileStream("cp932big.txt", fmRead)
    outStrm = newFileStream("utf-8big.txt", fmWrite)

  while true:
    let line = strm.readStr(1024)
    if line == "":
      break
    outStrm.writeLine conv.convert(line)

  outStrm.close
  strm.close
  conv.close

  echo &"streams readStr example: {cpuTime()-startTime} sec"

用意したダミーデータの情報は下記の通り。
500MB、700万行のテキストファイルです。

% file cp932big.txt
cp932big.txt: Non-ISO extended-ASCII text

% ls -lah cp932big.txt
-rw-rw-r-- 1 jiro4989 jiro4989 528M  4月 26 21:03 cp932big.txt

% wc -l cp932big.txt
7686144 cp932big.txt

このデータを食わせた実行結果は下記のとおりです。

system readFile example: 4.040268 sec
streams readLine example: 97.944166 sec
streams readStr example: 5.505938 sec

readLineが非常に遅いですがreadStrは早いです。
じゃあreadStrを使えばよいのか、というとそうは問屋が降ろさない。

CP932文字コードで問題になる日本語はマルチバイト文字です。
readStrの引数に指定するlengthはバイトサイズでの指定になっています。
よって、マルチバイトの文字単位では指定できません。

すると、マルチバイト文字の途中までしかreadStrできていないまま
文字コードの変換にかけてしまい、不正な文字になってしまうことがあります。

以下は前述の比較コードで生成されたUTF-8のテキストファイルの一部分です。
文字が途中で分断されていることがわかります。

% less utf-8big.txt
... 省略 ...

abcdefgああああああああああああああああああああああああああああああああ
abcdefgああああああああああああ<82>
<A0>あああああああああああああああああああ

データ構造的に速度が遅くても、あきらめてreadLineを使うのが無難です。
他に良い方法がアレば教えてください。

まとめ

  • 文字コードを変換する方法を学んだ
  • 巨大ファイルを扱う方法を学んだ

Nimユーザのお役に立てば幸いです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0