OSTRACISM CO.
C#とObjective-CとJavaと...
ファイル保存
GraphicGripGropは画像ファイルのサムネールをインデックスファイルとしてあらかじめ保存しておくことにより高速な検索を行う。
保存するファイルはシーケンシャルにどんどん読み捨てられるようにチャンク形式を採用する。
チャンクはヘッダ・パス・サムネールの3種類とし、最初に1回ヘッダがあり、残りは交互にパスとサムネールが続く。具体的なチャンクの構造は別項目を参照のこと。
C#
public class TChunk
{
public const string CKID_HEAD = "GGHD";
public const string CKID_PATH = "GGPT";
public const string CKID_THUMB = "GGTM";
public string m_ckID = "----";
public int m_ckSize = 0;
public byte[] m_data = null;
...
}
...
public virtual int Write(ref BinaryWriter bw) {
if (m_data == null)
return 0;
if (bw == null)
return 0;
UTF8Encoding utf8 = new UTF8Encoding();
byte[] id = utf8.GetBytes(m_ckID);
bw.Write(id, 0, 4);
m_ckSize = m_data.Length;
bw.Write(IPAddress.HostToNetworkOrder(m_ckSize));
bw.Write(m_data);
return 8 + m_ckSize;
}
チャンクのトップには32ビット整数の識別子が入る。Visual C++やXcodeのC/C++/Objective-Cのintはintの値として'ABCD'のような値の記述が可能で、MacintoshのOSType型などはそれを前提とした実装がなされている。
C#のintはそういった記述ができないので、文字列で識別子を保持し、書き込むときにbyteの配列に変換している。
整数値を書き込むときはホストのオーダー(x86はリトルエンディアン)からネットワークのオーダー(ビッグエンディアン)に変換をしている。System.Net.IPAddress.HostToNetworkOrderを使うが、IPAddressクラスという名前はあんまりだろう。
ファイルパス文字列からSystem.IO.FileStreamクラスインスタンスを、FileStreamクラスインスタンスからSystem.IO.BinaryWriterクラスインスタンスを得る。C標準ライブラリの方法に1段階抽象化が加わっており、フレームワークタイプのクラスライブラリはこういった傾向がある。
FileStreamクラスはファイルそのものへの操作を、BinaryWriterクラスはファイルへの様々なデータ形式の書き込み方をサポートしている。
Objective-C
#define CKID_HEAD 'GGHD'
#define CKID_PATH 'GGPT'
#define CKID_THUMB 'GGTM'
@interface TChunk : NSObject {
int m_ckID;
int m_ckSize;
NSData *m_data;
}
...
- (int)write:(NSFileHandle *)h {
if (m_data == nil)
return 0;
if (h == NULL)
return 0;
if ([m_data length] == 0)
return 0;
m_ckSize = [m_data length];
int d;
d = htonl(m_ckID);
[h writeData:[NSData dataWithBytes:&d length:sizeof(m_ckID)]];
d = htonl(m_ckSize);
[h writeData:[NSData dataWithBytes:&d length:sizeof(m_ckSize)]];
[h writeData:m_data];
return 8 + m_ckSize;
}
Objective-Cではチャンクトップの32bit整数の識別子はそのまま32bit整数で表現できる。
NSFileHandleクラスの書き込みはNSDataクラス経由でのみ用意されており、NSFileHandleクラスはファイルそのものへの操作を、NSDataクラスは様々なデータ形式のバイナリへの変換を担当している。
NSDataクラスはファイルI/Oのためのクラスではないが、結果的にC標準ライブラリの方法に1段階抽象化が加わっており、単なる32bit整数の書き込みもNSDataクラスを経由する必要がある。
整数値を書き込むときはホストのオーダー(PowerPCはビッグエンディアン、x86はリトルエンディアン)からネットワークのオーダー(ビッグエンディアン)に変換をしている。Cocoaライブラリを使った方法もあるのだが、ここではCのライブラリを使っている。
CPUのエンディアンの差異をここで吸収しているので、PowerPCでもx86でも出力データは同じになる(予定)。
32bit整数をlongでなくintで表現しているのは、Macintoshが今後64bit対応していくときにどこかのタイミングでLP64になる可能性があるからだ。現在32bit整数はintとlongの両方あり、64bit整数はlong longと表記するが、それは32bitから64bitへの移行期間中の暫定的な措置で、最終的には他のUnixに合わせLP64を採用することになると考えられる。
LP64ではintの意味が変更され、元々は「CPUにとって自然な整数」という意味だったのが「32bit整数」という意味に変わる。また、longは64bit整数になる。intとlongの大きさが環境依存でなく決定してしまうのだ。JavaやC#は最初からそうなっている。
ただし、GUI関連は32bitを当分維持するという観測もあり、LP64には移行できないのではないかという読みもある。Windows XP x64 EditionのWindows Platform APIはLP64を採用していない。Appleが発表したx86への移行もIA32と表現しており、IA64やx64(AMD64あるいはEM64T)ではない。Appleが最初に採用するであろうYonahはx64モードを持たない。
どちらにせよ現在32bit整数を表現するならintを使うのが最も危険が少ない。
G5環境でBSD向けの(非GUI)プログラムならMac OS X 10.4以降LP64を使うことができる。
Java
public class TChunk {
public static final String CKID_HEAD = "GGHD";
public static final String CKID_PATH = "GGPT";
public static final String CKID_THUMB = "GGTM";
public String m_ckID = "----";
public int m_ckSize = 0;
public ByteBuffer m_data = null;
...
}
int write(OutputStream stream) {
if (stream == null)
return 0;
byte[] d = null;
try {
ByteBuffer bb = ByteBuffer.allocate(8);
d = m_ckID.getBytes("UTF-8");
if (d == null || d.length < 4)
return 0;
bb.put(d, 0, 4);
m_ckSize = m_data.array().length;
bb.putInt(m_ckSize);
stream.write(bb.array(), 0, 8);
stream.write(m_data.array());
}
catch (Exception e) {
return 0;
}
return 8 + m_ckSize;
}
チャンクのトップには32ビット整数の識別子が入る。JavaもC#と同じくVisual C++やXcodeのC/C++/Objective-Cのような記述はできないので、文字列で識別子を保持し、書き込むときにbyteの配列に変換している。
C#ではエンコーダーに文字列を渡してバイト列を得ているが、Javaでは文字列がバイト列を得るメソッドを持っている。
java.io.FileOutputStreamクラスには直接32bit整数を書き込むメソッドはないので、java.nio.ByteBufferを経由している。
整数のオーダー変換の記述がないのは、JavaがどんなCPUでもオーダー順の変化がない環境を用意していて、また元々ネットワークとの親和性を設計段階で求めているためJavaのホストのオーダーはネットワークのオーダーと同じ(ビッグエンディアン)になっているため。
2005.07.31
「インデックス」へ戻る
OSTRACISM CO.
OSTRA / Takeshi Yoneki