ここでの焦点はファイル処理であるため、プログラムの内部データ構造からデータを読み取って標準およびカスタム形式のファイルに書き込む方法と、標準およびカスタム形式のファイルから読み取る方法にのみ関心があります。プログラムの内部データ構造。
このセクションでは、異なるファイル形式を直接比較できるように、すべての例に同じデータを使用します。
プログラムはコマンド ライン引数として 2 つのファイル名を受け入れます。1 つは読み取り用、もう 1 つは書き込み用です (これらは異なるファイルである必要があります)。プログラムは、最初のファイル (接尾辞が示す形式) からデータを読み取り、2 番目のファイル (これも接尾辞が示す形式) にデータを書き込みます。
invoicedata プログラムによって作成されたファイルはクロスプラットフォームです。つまり、形式に関係なく、Windows で作成されたファイルは Linux だけでなく Mac OS X でも読み取ることができ、その逆も同様です。 Gzip 圧縮ファイル (invoices.gob.gz など) はシームレスに読み書きできます。
データは []invoice、つまり請求書の値へのポインターを保持するスライスで構成されます。各請求書データはタイプ invoice の値に格納され、各請求書データは 0 個以上の項目を含む []*Item の形式で格納されます。
type Invoice struct {
Id int
Customerld int
Raised time.Time
Due time.Time
Paid bool
Note string
Items []*Item
}
type Item struct {
Id string
Price float64
Quantity int
Note string
}
これら 2 つの構造はデータを保持するために使用されます。以下の表は、非公式の比較を示しており、同じ 50,000 件のランダムな請求書を各形式で読み書きするのに必要な時間と、その形式で保存されたファイルのサイズを示しています。
タイミングは秒単位で測定され、10 分の 1 秒単位に切り上げられます。異なるハードウェアや異なる負荷条件では値が同じではないため、タイミング結果には絶対単位がないと考える必要があります。サイズ列はキロバイト (KB) 単位で表示され、すべてのマシンで同じである必要があります。
このデータセットでは、非圧縮ファイルのサイズは大きく異なりますが、圧縮ファイルのサイズは驚くほどすべて似ています。コードの機能には、すべての形式に共通のコード (圧縮と解凍、構造の定義など) は含まれません。
サフィックス | 読む | 書く | サイズ (KiB) | 読み取り/書き込みLOC | フォーマット |
---|---|---|---|---|---|
.gob | 0.3 | 0.2 | 7948 | 21 + 11 = 32 | binary |
.gob.gz | 0.5 | 1.5 | 2589 | ||
jsn | 4.5 | 2.2 | 16283 | 32+17 = 49 | JSON |
.jsn.gz | 4.5 | 3.4 | 2678 | ||
.xml | 6.7 | 1.2 | 18917 | 45 + 30 = 75 | XML |
.xml.gz | 6.9 | 2.7 | 2730 | ||
..TXT | 1.9 | 1.0 | 12375 | 86 + 53 = 139 | Plain text (UTF-8) |
.txt.gz | 2.2 | 2.2 | 2514 | ||
.inv | 1.7 | 3.5 | 7250 | 128 + 87 = 215 | custom binary |
.inv.gz | 1.6 | 2.6 | 2400 |
これらの読み取りおよび書き込み時間とファイル サイズは、プレーン テキストの読み取りおよび書き込みが驚くほど高速であることを除けば、合理的な予想の範囲内です。これは、fmt パッケージの優れた印刷およびスキャン機能と、私たちが設計した解析しやすいカスタム テキスト形式によるものです。
JSON および XML 形式の場合、デフォルトの time.Time 値 (ISO-8601 日付/時刻文字列) の代わりに日付部分を保存するだけで、速度をある程度犠牲にし、コード サイズを追加することでファイル サイズをわずかに削減します。
たとえば、JSON コードに time.Time 値自体を処理させると、より高速に実行でき、コードの行数は Go 言語のバイナリ エンコーディングと同様になります。
バイナリ データの場合は、Go 言語のバイナリ形式が最も使いやすいです。非常に高速かつ非常にコンパクトで、必要なコードはほとんどなく、データの変更にも比較的簡単に適応します。ただし、使用するカスタム型が gob エンコーディングをネイティブにサポートしていない場合は、その型を gob.Encoder および gob.Decoder インターフェイスを満たすようにする必要があります。これにより、gob 形式の読み取りと書き込みが非常に遅くなり、ファイル サイズが大きくなります。も拡大します。
人間が判読できるデータの場合、XML はおそらく使用するのに最適な形式であり、特にデータ交換形式として役立ちます。 XML 形式の処理には、JSON 形式の処理よりも多くのコード行が必要です。これは、Go には xml.Marshaler インターフェイスがないことと、ここでは XML データと請求書データ (請求書と項目) のマッピングを支援するために並列データのクラス (XMLInvoice と XMLItem) を使用しているためです。
XML を外部ストレージ形式として使用するアプリケーションは、invoicedata プログラムのような並列データや変換を必要としないため、invoicedata の例よりも高速で、必要なコードが少なくなる可能性があります。
読み取りと書き込みの速度、ファイル サイズ、コード行に加えて、フォーマットの堅牢性という考慮に値する別の問題があります。たとえば、Invoice 構造体とItem 構造体にフィールドを追加した場合、ファイル形式を再度変更する必要があります。私たちのコードはどのくらい簡単に新しい形式の読み取りと書き込みに適応し、古い形式の読み取りをサポートし続けることができるでしょうか?このような変更は、ファイル形式のバージョンを定義すれば (この章で演習として示します) 簡単に対応できますが、古い形式と新しい形式の読み取りと書き込みの両方に JSON 形式を適合させるのは少し複雑です。
Invoice とItem の構造に加えて、すべてのファイル形式は次の定数を共有します。
const(
fileType = "INVOICES" // テキスト形式用
magicNumber = 0xl25D // バイナリ形式用
fileVersion = 100 // すべての形式用
dataFormat = "2006-01-02" // 常にこの日付を使用する必要があります
)
magicNumber は、請求書ファイルを一意にマークするために使用されます。 fileVersion は、請求書ファイルのバージョンをマークするために使用されます。これは、データ形式の変更に適応するようにプログラムを変更するのに便利です。後で紹介する dataFormat は、人間が読める形式でデータをどのようにフォーマットするかを表します。
同時に、一対のインターフェースも作成しました。
type InvoiceMarshaler interface {
Marshallnvoices(writer io.Writer, invoices []*Invoice) エラー
}
type InvoiceUnmarshaler interface {
Unmarshallnvoices(reader io.Reader) ([]*Invoice, エラー)
}
この目的は、特定の形式に対してリーダーとライターを統一した方法で使用することです。たとえば、次の関数は、開いているファイルから請求書データを読み取るために、invoicedata プログラムによって使用されます。
func readinvoices(reader io.Reader、suffix string) ([]*Invoice、error) {
var unmarshaler InvoicesUnmarshaler
switch suffix {
case ".gob":
unmarshaler = GobMarshaler{}
case ".inv":
unmarshaler = InvMarshaler{}
case ".jsn"、".json":
unmarshaler = JSONMarshaler{}
case ".txt":
unmarshaler = TxtMarshaler{}
case ".xml":
unmarshaler = XMLMarshaler{}
}
if unmarshaler != nil {
return unmarshaler.Unmarshallnvoices(reader)
}
return nil、fmt.Errorf("Error: %s", suffix)
}
ここで、 Reader は io.Reader インターフェイスを満たす任意の値です。たとえば、開いているファイル ( *os.File タイプ) > gzip デコーダ ( *gzip.Reader タイプ)、または string.Readero サフィックスは、ファイル (.gz ファイルから解凍した後)。
次のサブセクションでは、メソッド MarshmlTnvoices() および Unmarshallnvoices() を提供する (したがって、InvoicesMarshaler および InvoicesUnmarshaler インターフェイスを満たす) GobMarshaler や InvMarshaler などのカスタム タイプについて説明します。