JISコードで書かれたテキストファイルをSHIFT-JISコードに変換したいため、以下のようなコードを書いて実行してみました。
100KBほどのテキストファイルならば素早く変換できるのですが、20MBぐらいのテキストファイルの場合、やはり時間がかかってしまいます。
フリーソフトの中には大きい容量のファイルでも2〜3秒で変換してしまうものがあります。どのような工夫が必要かどなたか教えてください。
//Jisコード1文字をSJisコード1文字に変換
function Jis_SJis(c0,c1: AnsiChar): AnsiString;
var
b0,b1,off: byte;
begin
b0 := Byte(c0);
b1 := Byte(c1);
Result := '';
if (b0 < 33) or (b0 > 126) then exit;
off := 126;
if b0 mod 2 = 1 then
if b1 < 96 then off := 31 else off := 32;
b1 := b1 + off;
if b0 < 95 then off := 112 else off := 176;
b0 := ((b0 + 1) shr 1) + off;
Result := AnsiChar(b0) + AnsiChar(b1);
end;
//Jisコードの文字列をSJisコードの文字列に変換
function JisToSJis(const s: AnsiString): AnsiString;
var
i: integer;
flg: boolean;
begin
flg := false;
Result := '';
i := 1;
while (i <= Length(s)) do
begin
if Copy(s,i,3) = #27 + '$B' then
begin
flg := true;
i := i + 3;
continue; //空の文字列がありえるので
end;
if (Copy(s,i,3) = #27 + '(B') or (Copy(s,i,3) = #27 + '(J') then
begin
flg := false;
i := i + 3;
continue; //空の文字列がありえるので
end;
if flg then
begin
if Length(s) > i then Result := Result + Jis_Sjis(s[i],s[i+1]);
inc(i);
end else
Result := Result + s[i];
inc(i);
end;
end;
大量テキスト操作で時間がかかるのは変換ではなく文字のつなぎ合わせです。
str := str + strA;
一回一回この操作をするたびにメモリー領域の再確保が行われているめです。
あらかじめ必要とするメモリーを一度に確保してしまえばびっくりするほど速くなります。
monaaさん、回答ありがとうございます。
ところで、メモリを一度に確保する、とは具体的にどうすればいいのですか?
Mr.XRAYです.
>大きい容量のファイルでも2〜3秒
その20MBぐらいのですか? やっばりWindows 95のマシンででしょうか? (笑)
文字列の操作を高速にする解決方法ではありませんが,文字コードの変換
でしたら,以下の様な方法もあります.参考までに.
テストした結果,JIS --> Shift-JIS のテキストの変換が,
60MBの場合 約5.3秒
30MBの場合 約2.6秒
でした.テスト環境は,ページ上部の記載の通りです.
マシンのスペックは秘密です.
http://mrxray.on.coocan.jp/Delphi/plSamples/886_ChangeCodePage.htm
メモリを一度に確保するってのは
var str:String;
SetLength(str,目的のサイズ);
とか、TMemoryStream.size := 目的のサイズ;
とか、array [0..目的のサイズ] of Byte
の事です。
Mr.XRAYさんのソースもMSのDLL参照ですが、
ほぼ確実にメモリは一度に確保していると想像できます。
上のソースを元にサンプルでもつくってあげたいところですが、
元気がないのでパスします。
monaaさん、できましたら簡単でもよろしいので、サンプルを作ってもらえませんか?
編集 削除こんな感じです。
速度と可読性を重視して書きました。
もっと早くできると思いますが、
サンプルということなのでご了承ください。
あと、ネットを徘徊すればjis-sjis変換は結構見つけることができると思います。
今回はググッて最初にみつかった「とほほのwww入門」を参考にしました。
http://www.tohoho-web.com/wwwkanji.htm
実用レベルとは思いますが、
Mr.XRayさんの紹介するコードより高速かどうか検証はしていません。(多分この程度では勝てません。)
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
//漢字コードの変換
procedure ConvertISO_2022_JPtoShiftJIS(var c1,c2:byte);
begin
if (c1 mod 2)=1 then
begin
c1 := ((c1 + 1) div 2) + $70;
c2 := c2 + $1f;
end else begin
c1 := (c1 div 2) + $70;
c2 := c2 + $7d;
end;
if (c1 >= $a0) then c1 := c1 + $40;
if (c2 >= $7f) then c2 := c2 + 1;
end;
function ISO_2022_JPtoShiftJIS(s:AnsiString; CRLF:Boolean):AnsiString;
type
TJisStyle = (jsNone,jsASCII,jsRoman,jsKana,jsOldKanji,jsNewKanji,jsOpKanji);
var
i,p,r,Len,skip:Integer;
b1,b2: Byte;
jStyle,jStyleConst:TJisStyle;
begin
{
ISO_2022_JP(7bit JIS)
00-1F,7F 制御コード
1B 28 42 - ESC ( B - 20-7E ASCII
1B 28 4A - ESC ( J - 20-7E JISローマ字
1B 28 49 - ESC ( I - 21-5F JISカナ(半角カナ)
1B 24 40 - ESC $ @ - 21-7E 21-7E 旧JIS漢字 (1978)
1B 24 42 - ESC $ B - 21-7E 21-7E 新JIS漢字 (1983/1990)
1B 24 44 - ESC $ D - 21-7E 21-7E JIS補助漢字
SJIS
00-1F, 7F 制御コード
20-7E JISローマ字(ASCII)
A1-DF JISカナ(半角カナ)
81-9F 40-7E JIS漢字
E0-EF 80-FC JIS漢字
改行コードの扱いについて
ISO_2022_JPの改行は $0A ですがTStringListで読み込むと $0D$0A に補完されます。
}
p:=1;
skip:=0;
Len := Length(s);
//文字バッファーを一度に確保
//メモリーサイズ jis >= sjisだからjis分確保すれば問題ないはず。
//改行コードを変換を入れるときは適当に大きめにする。
//これは大きいほど処理が早くなるがその分無駄にメモリーを消費する場合がある。
//あらかじめ改行をカウントすれば適切なサイズを得ることができるが、
//今回は速度重視のため、文字走査を一回にするため敢えてカウントしていません。
if CRLF then Len:= Round(Len*1.5);
SetLength(Result,Len);
jStyleConst := jsASCII;
for i := 1 to Len do //メインループ
begin
if skip = 0 then
begin
jStyle:=jsNone;
//エスケープシーケンス判定
if i < Len-3 then
if (byte(s[i]) = $1B) then
begin
if (byte(s[i+1]) = $28) then
begin
if (byte(s[i+2]) = $42) then
jStyle:=jsASCII else
if (byte(s[i+2]) = $4A) then
jStyle:=jsRoman else
if (byte(s[i+2]) = $49) then
jStyle:=jsKana;
end else
if (byte(s[i+1]) = $24) then
begin
if (byte(s[i+2]) = $40) then
jStyle:=jsOldKanji else
if (byte(s[i+2]) = $42) then
jStyle:=jsNewKanji else
if (byte(s[i+2]) = $44) then
jStyle:=jsOpKanji;
end;
end;
if jStyle <> jsNone then
begin
skip:=3;
jStyleConst:=jStyle;
end else
jStyle := jStyleConst;
//文字変換
case jStyle of
jsASCII,jsRoman
: begin
if (CRLF) and (byte(s[i+skip]) = $0A) then
begin
//改行コード
Result[p] := AnsiChar($0D);
Inc(p);
end;
Result[p] := s[i+skip];
Inc(p);
end;
jsKana : begin Result[p] := AnsiChar(byte(s[i+skip]) + $80); Inc(p) end;
jsOldKanji,
jsNewKanji,
jsOpKanji
: begin
b1:= Byte(s[i+skip]);
b2:= Byte(s[i+skip+1]);
ConvertISO_2022_JPtoShiftJIS(b1,b2);
Result[p]:=AnsiChar(b1); Inc(p);
Result[p]:=AnsiChar(b2); Inc(p);
inc(skip);
end;
end;
//改行コード変換付きの場合バッファが足りなくなる事があるのでチェックを入れます。
if (CRLF) and (p > Len-10) then
begin
Len:=Len + 10000;
SetLength(Result,Len);
end;
end else begin
Dec(skip);
end;
end;
Result[p]:=#0;
SetLength(Result,p);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
strList:TStringList;
s:AnsiString;
time:Cardinal;
begin
//TStringListを用いて読み込み
//改行を認識するため若干遅い
strList:= TStringList.Create;
strList.LoadFromFile('jis.txt');
time:=GetTickCount;
s:=ISO_2022_JPtoShiftJIS(strList.Text,False);
Caption:=IntToStr(GetTickCount - time) + 'ms';
//Memo1.Text:=s; //大容量ファイルの場合表示は省略
beep;
strList.Free;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
mStream:TMemoryStream;
s:AnsiString;
time:Cardinal;
begin
//TMemoryStreamを用いて読み込み
//TStringListより処理が簡素化しているため高速
mStream:=TMemoryStream.Create;
mStream.LoadFromFile('jis.txt');
mStream.Position:=0;
SetLength(s,mStream.Size);
mStream.Read(s[1],mStream.Size);
time:=GetTickCount;
s:=ISO_2022_JPtoShiftJIS(s,True);
Caption:=IntToStr(GetTickCount - time) + 'ms';
//Memo1.Text:=s; //大容量ファイルの場合表示は省略
beep;
mStream.Free;
end;
end.
>Mr.XRayさんの紹介するコードより高速かどうか検証はしていません。(多分この程度では勝てません。)
やつてみました.そんなことないですよ.
mlang.dllを利用する場合のオーバヘッドがありますからね.
以下測定結果です.使用したファイルは前と同じです.
どちらもプログラムを起動した一回目の測定結果で,結果の保存時間も含みます.
Button1の結果 2.261秒
Button2の結果 2.099秒
>使用したファイルは前と同じです.
30MBのほうです.
Mr.XRAYさん、わざわざ検証ありがとうございます。
意外と戦えたのには驚きです。
もうちょっと磨きをかければそこそこのレベルまで行きそうですね。
さっきのソースですが、今読み返したらLenが改行付の場合余計にループしていたので、そこだけ修正させてください。
あと、速度をもっと上げるなら余計な判定を削ったり、ConvertISO_2022_JPtoShiftJISのアセンブル化をしたりできそうです。
アセンブル化は私の守備範囲ではないですが。
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
//漢字コードの変換
procedure ConvertISO_2022_JPtoShiftJIS(var c1,c2:byte);
begin
if (c1 mod 2)=1 then
begin
c1 := ((c1 + 1) div 2) + $70;
c2 := c2 + $1f;
end else begin
c1 := (c1 div 2) + $70;
c2 := c2 + $7d;
end;
if (c1 >= $a0) then c1 := c1 + $40;
if (c2 >= $7f) then c2 := c2 + 1;
end;
function ISO_2022_JPtoShiftJIS(s:AnsiString; CRLF:Boolean):AnsiString;
type
TJisStyle = (jsNone,jsASCII,jsRoman,jsKana,jsOldKanji,jsNewKanji,jsOpKanji);
var
i,p,r,Len,Len2,skip:Integer;
b1,b2: Byte;
jStyle,jStyleConst:TJisStyle;
begin
{
ISO_2022_JP(7bit JIS)
00-1F,7F 制御コード
1B 28 42 - ESC ( B - 20-7E ASCII
1B 28 4A - ESC ( J - 20-7E JISローマ字
1B 28 49 - ESC ( I - 21-5F JISカナ(半角カナ)
1B 24 40 - ESC $ @ - 21-7E 21-7E 旧JIS漢字 (1978)
1B 24 42 - ESC $ B - 21-7E 21-7E 新JIS漢字 (1983/1990)
1B 24 44 - ESC $ D - 21-7E 21-7E JIS補助漢字
SJIS
00-1F, 7F 制御コード
20-7E JISローマ字(ASCII)
A1-DF JISカナ(半角カナ)
81-9F 40-7E JIS漢字
E0-EF 80-FC JIS漢字
改行コードの扱いについて
ISO_2022_JPの改行は $0A ですがTStringListで読み込むと $0D$0A に補完されます。
}
p:=1;
skip:=0;
Len := Length(s);
//文字バッファーを一度に確保
//メモリーサイズ jis >= sjisだからjis分確保すれば問題ないはず。
//改行コードを変換を入れるときは適当に大きめにする。
//これは大きいほど処理が早くなるがその分無駄にメモリーを消費する場合がある。
//あらかじめ改行をカウントすれば適切なサイズを得ることができるが、
//今回は速度重視のため、文字走査を一回にするため敢えてカウントしていません。
if CRLF then Len2:= Round(Len*1.5);
SetLength(Result,Len2);
jStyleConst := jsASCII;
for i := 1 to Len do //メインループ
begin
if skip = 0 then
begin
jStyle:=jsNone;
//エスケープシーケンス判定
if i < Len-3 then
if (byte(s[i]) = $1B) then
begin
if (byte(s[i+1]) = $28) then
begin
if (byte(s[i+2]) = $42) then
jStyle:=jsASCII else
if (byte(s[i+2]) = $4A) then
jStyle:=jsRoman else
if (byte(s[i+2]) = $49) then
jStyle:=jsKana;
end else
if (byte(s[i+1]) = $24) then
begin
if (byte(s[i+2]) = $40) then
jStyle:=jsOldKanji else
if (byte(s[i+2]) = $42) then
jStyle:=jsNewKanji else
if (byte(s[i+2]) = $44) then
jStyle:=jsOpKanji;
end;
end;
if jStyle <> jsNone then
begin
skip:=3;
jStyleConst:=jStyle;
end else
jStyle := jStyleConst;
//文字変換
case jStyle of
jsASCII,jsRoman
: begin
if (CRLF) and (byte(s[i+skip]) = $0A) then
begin
//改行コード
Result[p] := AnsiChar($0D);
Inc(p);
end;
Result[p] := s[i+skip];
Inc(p);
end;
jsKana : begin Result[p] := AnsiChar(byte(s[i+skip]) + $80); Inc(p) end;
jsOldKanji,
jsNewKanji,
jsOpKanji
: begin
b1:= Byte(s[i+skip]);
b2:= Byte(s[i+skip+1]);
ConvertISO_2022_JPtoShiftJIS(b1,b2);
Result[p]:=AnsiChar(b1); Inc(p);
Result[p]:=AnsiChar(b2); Inc(p);
inc(skip);
end;
end;
//改行コード変換付きの場合バッファが足りなくなる事があるのでチェックを入れます。
if (CRLF) and (p > Len2-10) then
begin
Len2:=Len2 + 10000;
SetLength(Result,Len2);
end;
end else begin
Dec(skip);
end;
end;
Result[p]:=#0;
SetLength(Result,p);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
strList:TStringList;
s:AnsiString;
time:Cardinal;
begin
//TStringListを用いて読み込み
//改行を認識するため若干遅い
strList:= TStringList.Create;
strList.LoadFromFile('jis.txt');
time:=GetTickCount;
s:=ISO_2022_JPtoShiftJIS(strList.Text,False);
Caption:=IntToStr(GetTickCount - time) + 'ms';
//Memo1.Text:=s; //大容量ファイルの場合表示は省略
beep;
strList.Free;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
mStream:TMemoryStream;
s:AnsiString;
time:Cardinal;
begin
//TMemoryStreamを用いて読み込み
//TStringListより処理が簡素化しているため高速
mStream:=TMemoryStream.Create;
mStream.LoadFromFile('jis.txt');
mStream.Position:=0;
SetLength(s,mStream.Size);
mStream.Read(s[1],mStream.Size);
time:=GetTickCount;
s:=ISO_2022_JPtoShiftJIS(s,True);
Caption:=IntToStr(GetTickCount - time) + 'ms';
//Memo1.Text:=s; //大容量ファイルの場合表示は省略
beep;
mStream.Free;
end;
end.