この形式を使用すると、1 から数十万までの任意の数の Java クラスを圧縮プログラムでエンコードすること、単一のバイトブロックでコンパクトに転送すること、および圧縮解除プログラムで復号化して同等の Java クラスファイルに変換することができます。また、クラスのリソースファイルやほかの副次的ファイルを表すこともできるので、一部の配備タスク、特に Java アプリケーションのダウンロードでは、JAR アーカイブの代わりに使用できます。
Pack200 形式では、圧縮されていない (「格納された」) クラスファイルを含んでいる同等の JAR ファイルと比較して、Java アプリケーションのサイズを 7 倍から 9 倍縮小できます。これに対し、JAR アーカイブや ZIP アーカイブに組み込まれている zip DEFLATE アルゴリズムを使用すると、得られる圧縮率は 2 倍になります。SDK ダウンロードの配備に以前使用されていたドキュメント化されていない「crunch」メカニズムでは、同様に 5 から 6 倍の圧縮率が得られます。ただし、これらの値はすべて、DEFLATE や類似の既製圧縮アルゴリズムによるポストパスを想定し、その効果を考慮しています。DEFLATE については、ここを参照してください。
この形式の主な目的は、Java アプリケーションのパッケージ化、転送、および配布に必要なディスク条件と帯域幅条件を軽減することです。以前のバージョンは、Java 2 Standard Edition リリース 1.4.1 および 1.4.2 (コード名「Hopper」および「Mantis」) のダウンロードをパッケージ化するために使用されていました。
この形式は、仮想マシンに高速でロードすることを目的としたものではなく、Java アプリケーションの実行時に起動速度やメモリーサイズを改善することもありません。直接ロード可能なファイル形式に関する技術要件が厳しいと、最適な圧縮は不可能になるでしょう。強く圧縮されたファイルの圧縮解除プログラムは複雑な処理を実行する必要があり、その処理のためにメモリーと CPU 時間を使用できることが必要です。Pack200 アーカイブは、Java SE および J2EE アプリケーションを実行する同種のマシンに展開されることを想定しています。
これはバッチ指向の形式で、Java クラスのパッケージ化と転送のために最適化されています。個別に格納されたクラスへのランダムアクセスはサポートしていません。このアーカイブ形式の連続的な性質を強調するために、圧縮プログラムで生成され圧縮解除プログラムで受け入れられるデータをフォーマットすることを表すのに、格納するではなく転送するという動詞を使用します。
この形式では、DEFLATE などのバイト指向の圧縮アルゴリズムで実行される処理は繰り返されません。通常、Pack200 を使用するツールは、アーカイブを ZIP ファイルに格納したり、ほかの圧縮技術を使用したりして、アーカイブをさらに圧縮します。Pack200 の設計では、このようなポストパス圧縮プログラムが存在することを想定しています。
この仕様は複雑で、一部の読者には必要以上に複雑に見えるかもしれません。ここに反映されている設計上の決定事項は、実際の製品に使用されている実際の Java クラスファイルで行なった広範なテストと実験に基づいています。この仕様から複雑さを取り除こうとすると、圧縮の効率もかなり低下することになるでしょう。
クラスファイル以外のファイルに対しては、場合によってファイルタイプ別の並べ替えが行われるほかには、特別な圧縮や変換は行われません。クラスファイル以外のイメージを保持しているアーカイブの場合、このようなファイルはポストパス圧縮プログラムで適切に圧縮されることを想定しています。
クラスは、個々の定数プールの代わりに、アーカイブ全体に使用される大きな定数プールを使用した形式で表されます。したがって、クラスファイルを Pack200 アーカイブから抽出すると、そのファイル用の新しい定数プールが作成され、定数プールの参照がすべて調整されます。これによってクラスのセマンティクスが変更されることはありませんが、通常はファイルのビット単位のイメージが変更されます。また、使用されていない定数プールエントリが削除されるため、そのサイズも変わることがあります。
各クラスファイルは、定数プールのインデックスをすべて検出して (あとで) 番号を再設定できるように、圧縮プログラムで完全に解析される必要があります。この要件は、定数を参照する任意のクラス属性、フィールド属性、メソッド属性、およびコード属性に当てはまります。Pack200 形式では、適度な範囲の属性がサポートされています。いくつかの種類の新しい属性レイアウトを圧縮プログラムに対して宣言でき、それらのレイアウトを圧縮解除プログラムで使用できるように Pack200 形式を介して転送できます。
論理的には、バンドは暗黙にサイズ指定される 32 ビットの符号 (プラスマイナス記号) のない整数の配列です。ただし、要素のエンコードは実際のバンド値をよりコンパクトに転送するために選択されるので、ほとんどの場合、物理的に必要となるのはバンドの要素あたり 1 バイトか 2 バイトです。
バンドには固定のヘッダーはなく、そのサイズを示すものもありません。バンドの要素数は、圧縮解除プログラムによって以前のバンドの内容から、あるいは (最終的に) アーカイブヘッダーから推定されます。
ある特定のバンドの要素には、共通の意味と役割があります。たとえば、転送されるすべてのクラスの名前は 1 つのバンドに保持され、クラスのフィールド数はすべて別のバンドに保持されます。各バンドは、アーカイブ内で連続したバイトセグメントとして転送されます。Pack200 アーカイブが最初からコンパクトでありながら zip などのユーティリティーによる圧縮性も高い主な理由は、この連続性にあります。
一部のバンドでは、各要素が 1 つのオブジェクトを記述します。たとえば、各クラスにはフィールド数が関連付けられ、これはアーカイブの class_field_count バンド内の対応する値で指定されます。ほかのバンドでは、バンド内で連続した 0 個以上の要素によって 1 つのオブジェクトが記述される場合もあります。たとえば、クラスで実装されているインタフェースは、アーカイブの class_interface バンド内で、対応する連続した 0 個以上の値によって指定されます。これらの値はそれぞれ、1 つのクラスを参照するインデックスです。
ごく少数のバンド (10 未満) には、リソースファイルのイメージなどの、均一でないバイトが保持されます。これらは byte バンドと呼ばれます。バンドの多くには、定数プールへの参照が保持されます。アクセス修飾子フラグと関連ビットを保持するバンドもあります。char バンドというバンドには、CHAR3 と呼ばれる (UTF8 に似ているが同一ではない) 特別に選択されたエンコードで、文字列の文字が保持されます。残りのバンドは、さまざまなほかの解釈とともに整数を転送します。
整数型バンドの 1 つ (cp_Utf8_big_chars) には、特殊処理のために選択された 1 つの CONSTANT_Utf8 文字列の文字が保持されます。ほかのバンドとは異なり、このバンドは、この特殊処理のために選択された文字列の数に応じて 0 回以上繰り返されます。特別に転送されるこれらの「大きな文字列」については、このドキュメントで後述します。
バンドのほとんどは、クラス内のメソッド数、フィールドの名前、getfield バイトコード命令のオペランドなどを転送するという、わかりやすい機能を持っています。
byte バンドの特殊ケースを除き、バンドはサイズ指定された一連のバイトと見なされることはなく、エンコードされた整数である要素が指定の数だけ連続したものと見なされます。通常、整数エンコードは (バイトシーケンスと見なした場合) 可変サイズなので、バンドの要素数からバンドのバイトサイズを求めるための確固とした規則はありません。実際、バンドの末尾を検出するには、バンドを 1 バイトずつ解析する必要があります。
リソースファイルイメージなどのバイト単位のデータをエンコードする byte バンドでは、整数は符号なし 8 ビットバイトとしてエンコードされます。この仕様では、このエンコードは BYTE1 と呼ばれます。クラスファイル定義のタイプ u1 と比較してください。
ほかのバンドは、はるかに広いダイナミックレンジの値を持ち、負の数が含まれる場合や、32 ビット符号なし最大値までの値が含まれる場合があります。表現に多くのバイトを必要とする大きな要素もありますが、一般的なバンド要素の絶対値は比較的小さいことを想定して、これらのエンコードのほとんどは可変長になっています。バンドの要素シーケンスに強い相関関係が現れると予測される場合、このようなバンドは、絶対数値としてではなく、連続する差分としてエンコード (デルタエンコード) されます。
各バンドには、圧縮プログラムと圧縮解除プログラムでそのバンドの要素を転送する際に使用されるプライマリエンコードが関連付けられます。byte バンドの場合を除いて、プライマリエンコードの代わりに使用するセカンダリエンコードを圧縮プログラムでオプションで指定できます。要するに、セカンダリエンコードが明示的に宣言されていないかぎり、バンドのエンコードにはプライマリエンコードがデフォルトで使用されます。これにより、Pack200 形式をバンド要素の実際の統計データにさらに適応させることができます。
たとえば、個数やサイズを保持するバンドなど、ほとんどのバンドで UNSIGNED5 というプライマリエンコードが使用されます。これは、範囲 [0..191] の値を 1 バイトとして表現する符号 (プラスマイナス記号) のないエンコードであり、一般的なものです。最大サイズ 5 バイトまで拡張して約 50,000,000 を超える数値を表すことができます。ただし、バンドに範囲 [0,255] の数値だけが含まれている場合は、BYTE1 エンコードの方がコンパクトなので、代わりにこれを使用するよう圧縮プログラムから圧縮解除プログラムに指示できます。
圧縮プログラムでセカンダリエンコードを指定する場合は、オプションのバンドコーディング指示子を発行する必要があります。これについては、別のセクションで説明します。カスタマイズされたエンコード方式を使用すると、バンドの要素値の実際のダイナミックレンジとより正確に一致する場合は、多くのバイトを節約できることがよくあります。
パラメータ B (1<=B<=5) は、1 つの整数のエンコードの最大長をバイト数で示します。常に下位ビットから先にバイトにエンコードされます。
パラメータ H (1<=H<=256) は、エンコードの基数であり、エンコードのバイトシーケンスが終了する条件も決定します。派生パラメータ L (0<=L<=255) は、(256-H) と定義されます。エンコードされたバイトシーケンスに保持できるのは、L より小さい値を持つ 1 バイトのみで、そのバイトはシーケンスの末尾でなければなりません。したがって、値 H が大きいほど、エンコード長の平均は長くなります。H が 256 で L が 0 の場合は、すべてのエンコード長がちょうど B バイトになるため、そのエンコード方式は固定長になります。
パラメータ S (0<=S<=2) は、エンコードが符号付き数値を表すかどうか、またどのように表すかを決定します。より正確には、通常バンド要素は 32 ビット符号なし整数と見なされるため、S が決定するのは、31 ビット符号なし整数の最大値 2147483647 より大きいバンド要素の符号化です。ただし、区別しても意味はないので、そのような数値は引き続き負の数値と呼ぶことにします。S は、符号ビットとして機能する最下位ビットの数を示します。S が 0 の場合、数値は符号なし数値です。S が 1 の場合は、符号なし数値の LSB と右シフトされた残り部分との排他的論理和によって、対応する符号付き数値が生成されます。S が 1 より大きい場合、生成された符号付き数値は、S の最下位ビットがすべて設定されている場合のみ負になります。それ以外の場合、これらの下位ビットは、その整数の正の絶対値に寄与します。この表現は、バイトコードの分岐オフセットなど、ほとんど正の数値を持つバンドに使用すると効果的です。符号ビットの解釈の詳細については、別のセクションで説明します。
パラメータ D (0<=D<=1) は、バンドがそのデータを連続する差分として転送 (デルタエンコード) するかどうかを決定します。このようなエンコードを記述する場合、S と D は両方とも 0 の場合に省略でき、D は 0 の場合に省略できます。
したがって、エンコード (1,256,0,0) は (1,256) と記述することもでき、これは符号なしバイトで数値を表現します。エンコード (4,256) は、リトルエンディアン 32 ビット符号なし整数で数値を表現します。エンコード (1,256,1) は、1 バイトを範囲 [-128,127] の数値にマッピングします。エンコード (1,256,1,1) は、連続する差分 (および最初の数値) が範囲 [-128,127] にある数値シーケンスを表現します。
もっとも一般的なエンコードである UNSIGNED5 は、32 ビット符号なし整数の全範囲を表現できます。このエンコードのバイトシーケンスは、192 より小さいバイト 4 つに任意の 1 バイトが続いたものか、192 より小さいバイト 0 - 3 つに 192 以上の 1 バイトが続いたものです。符号なしの値は、連続する各バイトを基数 64 のべき乗でスケーリングし、スケーリングされたバイト値をすべて加算することによって生成されます。
value | バイト 0 | バイト 1 | バイト 2 | バイト 3 | バイト 4 |
---|---|---|---|---|---|
1 | 1 | ||||
191 | 191 | ||||
192 | 192 | 0 | |||
193 | 193 | 0 | |||
255 | 255 | 0 | |||
256 | 192 | 1 | |||
512 | 192 | 5 | |||
1024 | 192 | 13 | |||
2048 | 192 | 29 | |||
12479 | 255 | 191 | |||
12480 | 192 | 192 | 0 | ||
798911 | 255 | 255 | 191 | ||
798912 | 192 | 192 | 192 | 0 | |
51130559 | 255 | 255 | 255 | 191 | |
51130560 | 192 | 192 | 192 | 192 | 0 |
0xFFFFFFFF | 255 | 252 | 252 | 252 | 252 |
1 つ以上のバンドで使用されているプライマリエンコードの一覧を次に示します。
型 | 名前 | 目的 |
---|---|---|
(1,256) | BYTE1 | bytes |
(3,128) | CHAR3 | Java 文字 |
(5,4) | BCI5 | バイトコードの位置 |
(5,4,2) | BRANCH5 | バイトコードの分岐オフセット |
(5,64) | UNSIGNED5 | 一般的な符号なし整数 |
(5,64,1) | SIGNED5 | 一般的な符号付き整数 |
(5,64,0,1) | UDELTA5 | 単調なシーケンス |
(5,64,1,1) | DELTA5 | 自己相関のあるシーケンス |
(5,64,2,1) | MDELTA5 | ほぼ単調なシーケンス |
各バンドを長々としたシーケンスで記述する代わりに、再帰的でない単純な文法を使用して系統的に表現します。この文法はクラスファイルの文法とほぼ同じです。
pack200_segment: segment_header *band_headers :BYTE1 cp_bands attr_definition_bands ic_bands class_bands bc_bands file_bands
この文法の終端語はスカラーとバンドです。終端語を認識しやすくするために、スカラー名はシャープ記号 (#)、バンド名はアスタリスク (*) で始めます。スカラーはエンコードされた整数 (またはその小さな固定値) で、アーカイブファイルのヘッダーに出現します。文法でスカラーを記述するときは、そのあとにコロン、スカラー値のエンコード、および角括弧で囲まれたスカラー値の個数を付加します。バンドを記述するときは、そのあとにコロン、バンドのプライマリエンコード、および角括弧で囲まれたバンドの長さを示すコメントを付加します。定数プールなどのほかのデータ構造への参照をエンコードする場合は、その構造を丸括弧内にコメントとして記述します。参照が null でもかまわない場合は、そのことも記述します。
次はその例です。
example_non_terminal: other_non_terminal #single_scalar_integer :UNSIGNED5[1] #four_byte_scalar :BYTE1[4] *band_of_integers :UNSIGNED5 [#integer_count] *band_of_method_references :UNSIGNED5 [SUM(*ref_counts)] (cp_Method) *band_of_strings_and_nulls :UNSIGNED5 [...] (null or cp_String)
多くの場合、バンドの長さは、単純に前述のスカラーを参照したものか ([#class_count] など)、個数を転送する前述のバンドの合計 ([SUM(*class_method_count)] など) になります。バンドの長さは仕様の文章で定義されるため、角括弧内の長さは文法ではコメントと見なす必要があります。バンドの個数は、省略記号を含んだ部分的な式で指定される場合もあるため、簡単に要約することはできません。
ほかの文法上の表記法を使用することもあります。通常の正規表現演算子 (X | Y) は、X または Y のいずれかとの一致を意味します。(X)* は、任意の数の X との一致を意味します。(X)+ は、1 つ以上の X との一致を意味します。(X)? は、0 または 1 つの X との一致を意味します。1 つのケースでは、バンドが複数のインスタンスを持つ場合があり、式 (band1) ** length(band2) は、band1 のインスタンスが band2 の要素数と同じ数だけ存在することを意味します。同様に、3 つのケースでは、式 (band1) ** #scalar は、ブール型のスカラー値 (0 または 1) に応じて、band1 のインスタンスがそれぞれ 0 または 1 つ存在することを示します。
pack200_archive: (pack200_segment)+各セグメントは、Pack200 マジックナンバーとバージョン番号で始まります。
(Pack200 アーカイブは、アーカイブを結合するという意味で互いに連結される場合があります。後述のとおり、各セグメントのヘッダーに #archive_size フィールドが存在しない場合は、それを挿入してから別のセグメントを追加する必要があります。これによって、圧縮解除プログラムで各セグメントの末尾を検出できます。セグメントヘッダーの形式は、この調整を簡単に実行できるようになっています。)
セグメンテーションをストリーミングトランスポート層と組み合わせると、圧縮プログラムの効率は低下しますが、圧縮プログラムで待ち時間を減らしたり、圧縮解除プログラムのメモリー要件を軽減したりすることができます。
セグメンテーションは、非常に大きなアーカイブ (数十メガバイト) のスケーリングに関連する問題の解決にも役立ちます。このようなアーカイブでは、結合されたグローバルな定数プールのインデックスの幅が大きくなりすぎ、定数をグローバルに共有する利点がなくなります。新しいセグメントを開始することで、圧縮プログラムは圧縮解除プログラムの定数プールをリセットして、不要な定数を削除します。ただし、引き続き使用される定数はふたたび引用する必要があります。このかね合いは、ガベージコレクタをコピーする設計と似たところがあります。
アーカイブのマジックナンバー (セグメントヘッダーの最初の 4 バイト) だけが固定サイズです。アーカイブのほかの構造体はすべて可変サイズなので、順に解析する必要があります。一般に、圧縮解除プログラムは、アーカイブの各セグメントを 2 回解析する必要があります。1 回目はバンドのサイズを求めて解析し、2 回目はバンドから情報を順番に抽出して各 JAR 要素に出力します。
segment_header: archive_magic archive_header archive_magic: #archive_magic_word :BYTE1[4] archive_header: #archive_minver :UNSIGNED5[1] #archive_majver :UNSIGNED5[1] #archive_options :UNSIGNED5[1] (archive_file_counts) ** (#have_file_headers) (archive_special_counts) ** (#have_special_formats) cp_counts class_counts archive_file_counts: #archive_size_hi :UNSIGNED5[1] #archive_size_lo :UNSIGNED5[1] #archive_next_count :UNSIGNED5[1] #archive_modtime :UNSIGNED5[1] #file_count :UNSIGNED5[1]
archive_magic_word は、0xCA、0xFE、0xD0、0x0D の 4 バイトで構成されます。#archive_minver は数値 1、#archive_majver は数値 160 でなければなりません。これら 2 つの値は、将来このファイル形式の小規模なリビジョンを反映して増分される場合があります。この規格の以前のバージョンでは、マイナーバージョン番号は 7、メジャーバージョン番号は 150 でした。
ヘッダーには、定数プールエントリおよびほかの「トップレベル」エンティティーの、初期の個数も含まれます。これらの個数は、すべて UNSIGNED5 形式で指定されます。これらの個数の一部は、#archive_options ワードのビットで制御される条件に応じて存在します。欠落しているヘッダー値 (および特に異なる記載がない場合は、欠落している値一般) に関する規則として、圧縮解除プログラムは明示的に値 0 を受け取った場合と同様に動作する必要があります。
ビット | 名前 | #archive_options で設定されている場合の意味 |
---|---|---|
0 | have_special_formats | archive_special_counts に個数が保持されています |
1 | have_cp_numbers | cp_number_counts に個数が保持されています |
2 | have_all_code_flags | code_flags_lo に各コードの要素が保持されています |
3 | (未使用、0 でなければならない) | |
4 | have_file_headers | archive_file_counts に個数が保持されています |
5 | deflate_hint | すべての要素について圧縮された JAR ファイルを要求します |
6 | have_file_modtime | file_modtime に更新時間が保持されています |
7 | have_file_options | file_options にオプションビットが保持されています |
8 | have_file_size_hi | file_size_hi に上位のサイズワードが保持されています |
9 | have_class_flags_hi | class_flags_hi に追加の属性フラグが保持されています |
10 | have_field_flags_hi | field_flags_hi に追加の属性フラグが保持されています |
11 | have_method_flags_hi | method_flags_hi に追加の属性フラグが保持されています |
12 | have_code_flags_hi | code_flags_hi に追加の属性フラグが保持されています |
13 | (未使用、0 でなければならない) | |
... | (未使用、0 でなければならない) | |
31 | (未使用、0 でなければならない) |
have_special_formats (LSB) が設定されている場合、バンド archive_special_counts には、文法に従って 2 つの要素が保持されます。それ以外の場合、このバンドに要素はありません。同様に、have_cp_numbers が設定されている場合、バンド cp_number_counts には、文法に従って 4 つの要素が保持されます。それ以外の場合、このバンドに要素はありません。have_all_code_flags が設定されている場合、バンド code_flags には、すべての Code 属性に対してそれぞれ 1 つの要素が保持される必要があります。それ以外の場合、このバンドの要素はより少なくなります。このオプションは、Code 属性に多くのサブ属性が含まれている場合に役立ちます。このような場合は、JAR に大量のデバッグ情報が含まれていると考えられるためです。have_file_headers が設定されている場合、バンド archive_file_counts には、文法に従って 5 つの要素が保持されます。それ以外の場合、このバンドに要素はありません。deflate_hint が設定されている場合、圧縮解除プログラムはその出力のサイズを小さくするよう要求されます (ただし必須ではない)。たとえば、圧縮解除プログラムで JAR ファイルを生成する場合は、JAR 要素を圧縮すればよいでしょう。オプション have_file_modtime、have_file_options、have_file_size_hi、have_class_flags_hi、have_field_flags_hi、have_method_flags_hi、have_code_flags_hi が設定されている場合は、下記のとおり、それぞれ対応するバンド file_modtime、file_options、file_size_hi、class_flags_hi、field_flags_hi、method_flags_hi、code_flags_hi が空でなくなることがあります。それ以外の場合、そのバンドに要素はありません。アーカイブオプションのほかのビットは、将来使用するために予約されており、0 でなければなりません。
#archive_options ワードの直後に 32 ビット数値のペアがあり、両方で 64 ビット長になります。圧縮解除プログラムはこれを利用して、アーカイブセグメントの末尾まで、セグメントを超えて読み込むことなく、入力データを簡単にバッファリングできます。値 #archive_size は、64 ビットの符号なしの値で、32 ビット符号なしワード #archive_size_lo と #archive_size_hi で構成されます。前者は下位の 32 ビットワード、後者は上位の 32 ビットワードです。値 #archive_size は 0 か、アーカイブセグメント内のバイト数を宣言します。開始位置は #archive_size_lo の直後かつ #archive_next_count の直前で、終了位置は最後のバンド *file_bits バンドです。つまり、サイズが 0 以外の場合は、#archive_next_count と *file_bits、およびその間にあるすべてのデータのサイズを示します。この値は冗長ですが、0 以外の場合は正しい値を圧縮プログラムで指定する必要があります。アーカイブセグメントがストリームの一部として転送される場合で、追加セグメントなどのほかのデータがアーカイブに続いて転送されるときは、#archive_size の値は 0 にすることはできません。この値は、圧縮解除プログラムで完全に無視される場合もありますが、入力データをより効率よくバッファリングするために一部の圧縮解除プログラムで利用される場合もあります。
#archive_size の直後にある値 #archive_next_count は、現在のアーカイブセグメントの直後から続くアーカイブセグメントの数の見積もりです。この数値は正確である必要はなく、常に 0 でもかまいません。残りの圧縮解除処理の分量について圧縮解除プログラムにヒントを提供するための値です。通常、これらのヒントは進捗バーに表示されます。
#archive_modtime の値が 0 以外の場合、圧縮解除プログラムはシステム固有の更新時間を出力用に調整するよう要求されます (ただし必須ではない)。たとえば、圧縮解除プログラムで JAR ファイルを生成する場合は、各 JAR 要素または JAR ファイル自体の更新日付をその日付に設定します。ただし、file_modtime の値が 0 以外の場合など、より詳細な指示がある場合を除きます。#archive_modtime の値は、System.currentTimeMillis で使用される元期 (1970 年 1 月 1 日 00:00:00 GMT) からの秒数と解釈されます。ただし、0 は特殊な値として予約されており、アーカイブ全体としての更新時間が存在しないことを示します。アーカイブの更新時間が存在しない場合、圧縮解除プログラムは代わりに任意の値を指定できます。この処理が行われる場合、file_modtime の値が存在するときは、これらの値は、圧縮プログラムによって指定される更新時間 #archive_modtime を基準にしていると解釈されます。
#file_count は、アーカイブによって詳細に記述されているファイルの数を示します。クラスファイルは単純で (下記を参照)、クラス自体を転送すれば十分なので、ファイルとして記述する必要はありません。したがって、転送されるクラスの数より #file_count が小さくなることもあります。転送されるクラスの数は #class_count と呼ばれます。一方、転送されるファイルがクラスを含んでいるとはかぎらないため、#file_count はクラスの数より大きくなることもあります。
各定数プールのカーディナリティーは、アーカイブヘッダーの cp_counts 構造体で UNSIGNED5 形式で指定されます。
cp_counts: #cp_Utf8_count :UNSIGNED5[1] (cp_number_counts) ** (#have_cp_numbers) #cp_String_count :UNSIGNED5[1] #cp_Class_count :UNSIGNED5[1] #cp_Signature_count :UNSIGNED5[1] #cp_Descr_count :UNSIGNED5[1] #cp_Field_count :UNSIGNED5[1] #cp_Method_count :UNSIGNED5[1] #cp_Imethod_count :UNSIGNED5[1] cp_number_counts: #cp_Int_count :UNSIGNED5[1] #cp_Float_count :UNSIGNED5[1] #cp_Long_count :UNSIGNED5[1] #cp_Double_count :UNSIGNED5[1] archive_special_counts: #band_headers_size :UNSIGNED5[1] #attr_definition_count :UNSIGNED5[1] class_counts: #ic_count :UNSIGNED5[1] #default_class_minver :UNSIGNED5[1] #default_class_majver :UNSIGNED5[1] #class_count :UNSIGNED5[1]これらの数が発生する順序は、定数プール自体がアーカイブで転送される順序と同じです。この順序は、定数プールの定義順序と呼ばれます。
4 つの数値定数プールのサイズは cp_number_counts で指定されます。これらが指定する数値は、ldc バイトコードで使用されることがあります。このようなバイトコードを実際に使用するクラスはわずかなので、これらのサイズはオプションであり、#have_cp_numbers によって制御されます。
各クラスファイルのヘッダーには、マジックナンバー、マイナーバージョン番号、およびメジャーバージョン番号が含まれています。マジックナンバー (0xCAFEBABE) は固定の定数で、Pack200 アーカイブでは転送されません。ただし、バージョン番号は変更される場合があるため、記録する必要があります。
特定のバージョン番号が主に使用されることを想定して、アーカイブヘッダーではデフォルトのメジャーバージョン番号とマイナーバージョン番号が転送されます。これらの番号は、どちらも UNSIGNED5 形式で指定されます。
アーカイブ内の個々のクラスでは (下記を参照)、独自のバージョン番号を擬似属性でオプションで指定できます。これは、アーカイブヘッダーで指定されている #default_class_minver と #default_class_majver をオーバーライドします。
#band_headers_size は、band_headers のサイズをバイト数で指定します。これらのバイトは、セカンダリ符号化のバンドコーディング指示子の定義に役立ちます。その形式の詳細については、メタ符号化に関するセクションで説明します。
アーカイブヘッダーでは、属性の型の数 (#attr_definition_count)、クラス定義の数 (#class_count)、およびネストされたクラス宣言の数 (#ic_count) も指定されます。これらの数は、ほかの各種のバンドの定義に従って、これらのバンドのサイズを求めるために使用されます。
12 の定数プールのカウントをすべて算術的に合計した値は、536870912 (2^29) より小さくなければなりません。合計がこの制限値以上になるようなアーカイブを圧縮プログラムで転送することは禁止されます。この制約の目的は、圧縮解除の実行中に特定の定数 (分解解除された内部クラス名や暗黙的な SourceFile 名など) を定数プールに追加する必要がある場合においても、圧縮解除プログラムが内部で統一された定数の番号付けを使用できるようにすることです。
実装にあたっての注意:cp_counts では 12 個までの数値が転送されるため、アーカイブヘッダーでは 26 個までの 32 ビット整数が転送されます。 セグメントヘッダーには、オプションのサブバンドが 3 つあり、それぞれ 2 つ、5 つ、および 4 つの値が含まれます。したがって、segment_header の最小サイズは 19 バイト、仮定上の最大サイズは 134 バイトです。 さらに、圧縮解除プログラムでは、誤った先読みを避けるために、#archive_size フィールドが確実に含まれている最初の 19 バイトのブロックを読み込んでバッファリングすることもできます。これはバージョン番号が実際と同様に小さいことを前提としています。その直後に、アーカイブの残りを走査するために追加で必要となるバッファーストレージのバイト数を判定するために、(少なくともリソースファイルイメージまで) 必要な情報を解析します。*file_bits 内のリソースファイルイメージを解析するときまでには、圧縮解除プログラムは *file_size バンドの読み込みを完了しており、最初の #archive_size に存在していた不確実性をすべて取り除くことができます。#archive_size フィールドが 0 の場合は、圧縮解除プログラムでアーカイブを解析するには、入力チャネルのすべてのデータを終わりまで読み込む必要が生じることもあります。
定数プールには、すべての入力クラスファイルの定数プールにある情報が統合されます。各定数プールには、1 つの型の定数値が含まれます。クラスファイルで使用される 11 の型の定数プールは、それぞれ独自の定数プールに入れられます。12 番目のプールにはシグニチャーが保持されます。シグニチャーは、クラスファイルではメソッドやフィールドの型を表す UTF8 文字列ですが、Pack200 アーカイブでは個別に圧縮されたデータ型です。
次の表に、12 の定数プールをその定義順序に従って示します。Java クラスファイル形式で使用される定数型との対応も定義します。
名前 | クラスファイルの要素 | クラスファイルのタグ | 目的 |
---|---|---|---|
cp_Utf8 | ConstantPool.cpUtf8 | CONSTANT_Utf8 | 基本的な文字列データ |
cp_Int | ConstantPool.cpInteger | CONSTANT_Integer | int 型の定数 |
cp_Float | ConstantPool.cpFloat | CONSTANT_Float | float 型の定数 |
cp_Long | ConstantPool.cpLong | CONSTANT_Long | long 型の定数 |
cp_Double | ConstantPool.cpDouble | CONSTANT_Double | double 型の定数 |
cp_String | ConstantPool.cpString | CONSTANT_String | String 型の定数 |
cp_Class | ConstantPool.cpClass | CONSTANT_Class | クラスの参照 |
cp_Signature | (次を参照) | (なし) | メソッド、フィールド、または変数の型 |
cp_Descr | ConstantPool.cpNameAndType | CONSTANT_NameAndType | 名前と型のペア |
cp_Field | ConstantPool.cpFieldref | CONSTANT_Fieldref | フィールドの参照 |
cp_Method | ConstantPool.cpMethodref | CONSTANT_Methodref | メソッド呼び出し |
cp_Imethod | ConstantPool.cpInterfaceMethodref | CONSTANT_InterfaceMethodref | インタフェース呼び出し |
(なし) | CONSTANT_Unicode | 未使用の型 |
論理的には、各定数プールは、すべて同じ 1 つの型を持つ記号値または数値のセットです。Pack200 アーカイブでは、定数プールはこれらの値のシーケンスとして構成され、各値にシーケンス内で一意のインデックスが付けられています。アーカイブで定数を記述する必要があるときは、該当する定数プール内 (またはプールのサブセット内) のインデックスが指定されます。プールのサブセットは、プールへの参照を含んでいるバンドとともに渡されるときに記述されます。
クラスファイル形式とは異なり、Pack200 アーカイブ形式では定数プールのインデックスは 0 から始まります。null 参照が想定されるまれなケースでは、その可能性が記載され、null 参照自体は 0 のインデックスでエンコードされます。ほかのすべてのインデックス値は 1 増加します。null 参照が想定されていないケースでも、null 参照は 32 ビットのインデックス値 -1 でエンコードされる場合があります。
また、クラスファイルとは異なり、long 値や double 値に関連して未使用のインデックス (ギャップ) が発生することはありません。定数プールの要素を正式に参照するために、配列の表記法を使用することもあります。たとえば、cp_Int 定数プールの最初の 3 つの整数は cp_Int[0]、cp_Int[1]、cp_Int[2] で、最後の整数は cp_Int[cp_Int_count-1] です。この仕様では、バンドと定数プールはどちらも 0 から始まる配列として扱われます。
圧縮プログラムで同じ定数を 2 回転送することは不正です。つまり、定数プールのエントリはすべて一意でなければなりません。
クラスファイルでは、少数の例外を除き、定数プールの参照は強く型付けされます。つまり、どの参照も、参照のコンテキストに関連付けられた固定タグの定数プールエントリを参照するか、null になります。例外は、ConstantValue 属性と、ldc、ldc_w、および ldc2_w 命令のオペランドフィールドです。
ただし、Pack200 アーカイブの定数プールには個別にインデックスが設定されるので、ある特定のインデックスが適用される定数プール (またはプールのサブセット) は 1 つだけになるように、定数の参照はすべて強く型付けする必要があります。そのためには、コンテキストから定数の型を推定するか (ConstantValue 属性の場合)、参照のコンテキストにほかの情報を追加します。バイトコードストリーム内で、ldc は定数型によって aldc、ildc などの個別の命令に分割されます。
アーカイブ内の定数プールバンドのレイアウトは、定数プール自体の定義順序に従います。定数プールもまた、それぞれ 1 つ以上のバンドのシーケンスで表現されます。定数プールバンドのシーケンスは次のような構造になります。
cp_bands: cp_Utf8 *cp_Int :UDELTA5 [#cp_Int_count] *cp_Float :UDELTA5 [#cp_Float_count] cp_Long cp_Double *cp_String :UDELTA5 [#cp_String_count] (cp_Utf8) *cp_Class :UDELTA5 [#cp_Class_count] (cp_Utf8) cp_Signature cp_Descr cp_Field cp_Method cp_Imethod
ここに示されているように、1 つの 32 ビット整数または 1 つの参照からエントリを派生できる定数プールは、それぞれ 1 つのバンドで表されます。その他の定数プールは、バンドの集まりで表されます。各定数プールの対応する文法要素には、プールの名前が付けられます。この例の cp_bands では、名前の元となっている定数プールの定数を転送する各バンドまたはバンドグループの順序が宣言されています。
いくつかのバンドで UDELTA5 がプライマリエンコードとして使用されていることに留意してください。Pack200 ファイル形式では、定数プールの値を特定の順序に並べる必要はありませんが、バンド内で UDELTA5 でエンコードされた値が単調に増加するように並べると、通常は有利になります。負の差分を UDELTA5 でエンコードすることは可能ですが、負荷が高くなります。また、UDELTA5 の代わりに、符号付きのセカンダリエンコードを圧縮プログラムで選択することもできます。出力順序の選択肢が与えられた場合に、高品質の圧縮プログラムはバンドのプライマリエンコードを使用して良い結果が得られるように選択すると想定して、この仕様ではプライマリエンコードを選択しています。
cp_Int 定数プールの値は、そのバンド内のエンコードされた値で直接表されます。cp_Float 定数プールの値は、そのバンド内のエンコードされた 32 ビット値に java.lang.Float.intBitsToFloat を適用した場合と同様に取得されます。
cp_Long バンドの 64 ビット値は、2 つのバンド cp_Long_hi および cp_Long_lo の対応する 32 ビット値を上位ワードおよび下位ワードとして連結することによって取得されます。これらのバンドには、それぞれ上位ワードと下位ワードが含まれています。同様に、cp_Double バンドの値は、まずほかの 2 つのバンドから上位ワードおよび下位ワードを連結し、java.lang.Double.longBitsToDouble を適用した場合と同様に、得られた 64 ビット整数の型を指定し直すことによって取得されます。上位ワードと下位ワードは、それぞれ cp_Double_hi バンドと cp_Double_lo バンドに含まれています。
cp_Long: *cp_Long_hi :UDELTA5 [#cp_Long_count] *cp_Long_lo :DELTA5 [#cp_Long_count] cp_Double: *cp_Double_hi :UDELTA5 [#cp_Double_count] *cp_Double_lo :DELTA5 [#cp_Double_count]
最終的に浮動小数点値をクラスファイルに書き込むときは、java.lang.Float.floatToRawIntBits または java.lang.Double.doubleToRawLongBits で処理した場合と同様に、そのビットを正確に格納する必要があります。このようにすると、NaN 値が正規化されることなく、忠実に転送されます。
(注:Pack200 のバンドコーディング技術を拡張して 64 ビット値を直接エンコードすることもできますが、このファイル形式では、64 ビット値は常に 32 ビット値のペアとして、バンドのペアで転送されます。これによって実装が単純になり、32 ビットのデータパスでバンドデータを処理することが可能になります。
cp_String 定数プールの各文字列は、そのバンド内で、cp_Utf8 定数としての文字列のスペルへの参照によって表されます。
同様に、cp_Class 定数プールの各クラスは、そのバンド内で、cp_Utf8 定数としてのクラスのスペルへの参照によって表されます。クラスファイルや VM の場合と同様に、スペルではパッケージの接頭辞要素を区切るためにスラッシュ文字列が使用されます。
cp_Utf8 が空でない場合、その最初の値は常に空の文字列です。したがって、その空の文字列には常にインデックス 0 が与えられます。この空の文字列を表すためのバンド値は転送されません。バンド cp_Utf8_prefix および cp_Utf8_suffix の値は、cp_Utf8 の空の文字列のあとの文字列に関連します。定数の重複は許可されていないため、cp_Utf8 のほかのすべての要素には少なくとも 1 文字が保持されます。
cp_Utf8 定数プールの各文字列は、アーカイブファイルでは接頭辞および接尾辞の 2 つの部分で表されます。接尾辞は、個数と文字コードシーケンスの両方で表されます。接頭辞は個数だけで表されます。接頭辞の文字は、前の文字列の繰り返しです。この手法により、圧縮プログラムでは、連続する差分を使用して文字列を表すこともできます。接尾辞の最初の値と接頭辞の最初の 2 つの値は、転送されるアーカイブでは省略されます。転送されるとしたら、これらの値は常に 0 になります。cp_Utf8 の最初の文字列は常に空で、2 番目の文字列は空でない接頭辞を最初の文字列と共有することはできないためです。
各接尾辞は、小接尾辞または大接尾辞のどちらかとして転送されます。小接尾辞は 1 つの大きなバンドで連続して転送されます。大接尾辞はまれですが、例外的な統計データを持つ文字列をコンパクトに転送するために使用されます。たとえば、実際には数値データのテーブルである文字列などに使用されます。大接尾辞は、それぞれ独自のバンドで転送されます。これにより、例外的な大きな文字列ごとに、含まれている文字に応じて効率的にカスタマイズされた、セカンダリ符号化を圧縮プログラムで選択できます。
cp_Utf8: *cp_Utf8_prefix :DELTA5 [MAX(0,#cp_Utf8_count-2)] *cp_Utf8_suffix :UNSIGNED5 [MAX(0,#cp_Utf8_count-1)] *cp_Utf8_chars :CHAR3 [SUM( *cp_Utf8_suffix )] *cp_Utf8_big_suffix :DELTA5 [COUNT(0, *cp_Utf8_suffix )] (*cp_Utf8_big_chars :DELTA5) ** length(cp_Utf8_big_suffix) [SUM( *cp_Utf8_big_suffix )](注:この仕様では必須ではありませんが、文字列を辞書式順序で並べ、隣接する文字列どうしで最大限の共通接頭辞が見つかるようにすると役立ちます。接頭辞の共有機能を無視してすべての接頭辞を 0 と宣言することもできます。
バンド cp_Utf8_suffix には、#cp_Utf8_count - 1 個の要素が含まれます。このバンドでは、定数プールの各文字列の小接尾辞の長さが順番に指定されます (最初の暗黙的な空の文字列は除く)。バンド cp_Utf8_prefix には、#cp_Utf8_count - 2 個の要素が含まれます。このバンドでは、定数プールの各文字列の接頭辞の長さが順番に指定されます (接頭辞の長さが常に 0 となる最初の 2 つの文字列は除く)。接頭辞の長さは、cp_Utf8[i-1] と cp_Utf8[i] の両方で共有される接頭辞サブ文字列の長さをエンコードしたものです。cp_Utf8 内の文字列が 3 つより少ない場合、cp_Utf8_prefix は空になります。接頭辞の長さは、全体の長さの大部分を占めることが通常で、半分を占める場合もあります。
バンド cp_Utf8_chars の各値は、Java 文字を表す 16 ビットの数値です。このバンドには、すべての小接尾辞の文字が順番に含まれています。連続する各文字列について、その小接尾辞の文字をエンコードする追加的な一連の値がある場合、それらの値は cp_Utf8_chars に保持されます。したがって、このバンド全体の長さは、cp_Utf8_suffix バンド内のすべての値を合計したものです。
定数プールのエントリの小接尾辞の長さが 0 である場合、その文字列は小接尾辞ではなく大接尾辞を持っています。各大接尾辞の長さは、cp_Utf8_big_suffix バンドの 1 要素で指定されます。したがって、このバンドの長さは、cp_Utf8_suffix バンド内の 0 値の個数と正確に一致します。各大接尾辞は、16 ビットの文字値から成る個別のバンドとして転送され、バンドの 1 要素が 1 文字を表します。各大接尾辞に、このようなバンドが 1 つ存在します。これらのバンドは cp_Utf8_big_suffix バンドの直後にあり、まとめて cp_Utf8_big_chars バンドと呼ばれます。通常、同じ型のデータは 1 つのバンドにまとめられますが、これらの文字列は独立してエンコードできるよう、個別のバンドに入れられます。このような文字列は通常、実際の Java 文字ではなくバイナリデータの配列をエンコードします。
大接尾辞の長さが 0 になることもあります。これは、その定数プールエントリが前の文字列の空でない接頭辞であることを意味します。そのような場合、対応する接尾辞バンドはアーカイブファイル内で領域を占有しません。長さ 0 のバンドは領域をまったく占有しないためです。
この概念については、「付録」セクションの「擬似コード」を参照してください。
シグニチャー定数は、cp_Class 定数への埋め込み参照を保持できるという点で特殊です。埋め込まれているクラス参照がスペルに展開されたあとは、シグニチャー定数は Utf8 定数と等価です。シグニチャー定数は、柔軟性が高く任意の文字列を表すことができますが、大文字の 'L' に続くクラス名を保持することの多い文字列に使用されます。このような文字列には、フィールド、メソッド、およびローカル変数の型、ジェネリック Signature 属性などがあります。
各シグニチャー文字列は、1 つのフォームと、0 個以上のクラス参照のシーケンスに分解されます。クラス参照を取得するために、元のシグニチャー文字列で Lclassname というパターンに一致するすべてのシーケンスが検索され、classname 部分が削除されます。これが cp_Class 定数プールへの参照として扱われます。フォームは、元の文字列からクラス名が削除されたあとの残りとして定義されます。
フォームには、削除されたクラス名をマークするもの以外に、文字 'L' を含むことはできません。
したがって、フォームには、元の型文字列でクラスが参照されている各位置に、文字 'L' が含まれることになります。圧縮解除プログラムでは、フォーム内のこれらの文字を数えることにより、元の型文字列で参照されているクラスの数を推定できます。この数は、フォームのクラス長と呼ばれます。
cp_Class の定数は、既存のクラスを参照する必要はなく、有効なクラス名のスペルを保持する必要すらありません。したがって、圧縮プログラムでは、シグニチャー文字列から抽出するクラス名がある場合、その選択の自由度がかなり高くなります。極端な場合、各フォームをその対応するシグニチャー文字列と同一にし、空の名前を持つ架空のクラスへの参照を発行してクラス長を満足させることもできます。このクラス長には、クラス名自体に含まれる 'L' の発生数もすべて含める必要があります。
また、Signature 属性のジェネリック型のインスタンスの場合、クラス名のあとには通常はセミコロンか左山括弧が続きますが、これらのエンコード規則で必須の文字が指定されているわけではありません。
規則では、各 'L' 文字のあとに文字がある場合にそのいくつをクラス名の一部として転送するかも、圧縮プログラムで任意に決定できるようになっています。したがって、1 つのシグニチャー文字列がいくつかのフォームで表される場合もあり、圧縮解除プログラムはそれらすべてを処理できるよう準備する必要があります。
いくつか例を挙げます。
型のシグニチャー文字列 | フォーム | クラス 長 |
クラス |
---|---|---|---|
F | F | 0 | |
[Z | [Z | 0 | |
[[[LLL; | [[[L; | 1 | LL |
[[[LLL; | [[[LLL; | 3 | (空)、(空)、(空) |
([Ljava/lang/String;)V | ([L;)V | 1 | java/lang/String |
Ljava/util/List<Lpkg/Item;>; | L<L;>; | 2 | java/util/List、pkg/Item |
(Ljava/lang/String;II)Lpkg/Item; | (L;II)L; | 2 | java/lang/String、pkg/Item |
Ljava/util/List<Ljava/lang/Byte;>; | L<L;>; | 2 | java/util/List、java/lang/Byte |
<ELEM:>(Ljava/util/List<TELEM;>;)TELEM; | <EL:>(L<TEL;>;)TEL; | 1 | EM、java/util/List、EM、EM |
ALLOWABLE | ALLOWABLE | 3 | (空)、(空)、(空) |
ALLOWABLE | AL | 1 | LOWABLE |
ALLOWABLE | ALABL | 2 | LOW、E |
cp_Signature 定数プール内のすべての文字列のフォームは、cp_Signature_form バンド内で順番に指定され、各シグニチャー文字列ごとに 1 つの cp_Utf8 参照で表されます。各フォームについて、シグニチャー文字列を再構成するために必要なクラスのシーケンスが、クラス長と同じ長さだけ、cp_Signature_classes バンド内の連続した cp_Class 参照として転送されます。これらの定義の結果として、cp_Signature_classes バンドの長さは、cp_Signature_form に記述されているフォームのスペルシーケンスに発生する 'L' 文字の合計数になります。
cp_Signature: *cp_Signature_form :DELTA5 [#cp_Signature_count](cp_Utf8) *cp_Signature_classes :UDELTA5 [COUNT('L',...)] (cp_Class)
この概念については、「付録」セクションの「擬似コード」を参照してください。
同様に、cp_Field、cp_Method、および cp_Imethod の各定数は、クラスと名前・型記述子のペアを順番に並べたものです。クラスは cp_Class 参照、名前・型記述子は cp_Descr 参照で表されます。これらの参照も、関連付けられているバンドの対応する要素で転送されます。
cp_Descr: *cp_Descr_name :DELTA5 [#cp_Descr_count] (cp_Utf8) *cp_Descr_type :UDELTA5 [#cp_Descr_count] (cp_Signature) cp_Field: *cp_Field_class :DELTA5 [#cp_Field_count] (cp_Class) *cp_Field_desc :UDELTA5 [#cp_Field_count] (cp_Descr) cp_Method: *cp_Method_class :DELTA5 [#cp_Method_count] (cp_Class) *cp_Method_desc :UDELTA5 [#cp_Method_count] (cp_Descr) cp_Imethod: *cp_Imethod_class :DELTA5 [#cp_Imethod_count] (cp_Class) *cp_Imethod_desc :UDELTA5 [#cp_Imethod_count] (cp_Descr)
クラスファイルの内容が Pack200 で圧縮される場合、そのクラスファイルは特別なオプションビットで「スタブ」としてマークされます。クラススタブファイルは、長さ 0 を持つように宣言される必要があります。名前として空の文字列を持つように宣言される場合もあります。転送されるクラススタブファイルの数が、転送されるクラスの数に満たない場合、圧縮解除プログラムは、圧縮プログラムから一連の無意味なスタブ、たとえば名前や内容、アーカイブヘッダーからコピーされる更新時間やデフレーションヒントを持たないスタブが、追加で転送された場合と同様に動作する必要があります。
Pack200 アーカイブでは、各リソースファイルは単純なバイト単位のイメージとして、相対パス名で転送されます。相対パス名のディレクトリの区切り文字には、スラッシュ (/) が使用されます。このパス名の規則は、ZIP アーカイブで使用されるものと同じです。リソースファイルとクラスファイルのどちらの場合も、各ファイルにデフレーションヒントや更新日付をオプションで指定できます。
file_bands: *file_name :UNSIGNED5 [#file_count] (cp_Utf8) *file_size_hi :UNSIGNED5 [#file_count*(#have_file_size_hi)] *file_size_lo :UNSIGNED5 [#file_count] *file_modtime :DELTA5 [#file_count*(#have_file_modtime)] *file_options :UNSIGNED5 [#file_count*(#have_file_options)] *file_bits :BYTE1 [SUM(*file_size)]
コメント、余分な属性、CRC 値などの追加的なファイル属性はありません。アプリケーションで一部のファイルにそのような属性を使用する必要がある場合は、ファイルを適切なアーカイブファイル形式 (ZIP など) にエンコードし、それらのアーカイブを Pack200 アーカイブでリソースファイルとして転送できます。
各ファイルの名前は file_name バンドの要素として転送され、長さ (バイト数) は file_size_lo バンドと file_size_hi バンド (存在する場合) の対応する要素で転送されます。これら 3 つのバンドの長さはすべて #file_count です。ただし、#archive_options の have_file_size_hi ビットが設定されていない場合、file_size_hi の要素数は 0 になります。
各ファイルの長さはバイト数で表され、file_size_hi バンドが空の場合は、file_size_lo バンドから取得される対応する 32 ビット符号なし値と一致します。それ以外の場合、ファイルの長さは 64 ビットの符号なしの値で、file_size_lo バンドと file_size_hi バンドの対応する 32 ビット符号なし要素で構成されます。前者の値は下位の 32 ビットワード、後者の値は上位の 32 ビットワードです。
クラスファイル以外のファイル (つまりリソースファイル) のバイトは、file_bits バンド内で直後に続きます。各ファイルは、対応する連続したバイト値として指定されます。
オプションのバンド file_modtime は、空でない場合、各リソースファイルの更新時間を指定します (クラスファイルスタブが存在する場合はクラスファイルの更新時間を指定する場合もあります)。各整数値は、#archive_modtime の値との差を秒数で示します。したがって、#archive_modtime の値が 0 の場合は、個々のファイル時間を絶対値として解釈する必要があります。file_modtime の転送順序は、file_name の転送順序と一致します。#archive_options の have_file_modtime ビットが設定されていない場合、このバンドは空になります。
オプションのバンド file_options は、空でない場合、各リソースファイルのフラグビットを指定します (クラスファイルスタブが存在する場合はクラスファイルのフラグビットを指定する場合もあります)。#archive_options の have_file_options ビットが設定されていない場合、このバンドは空になります。file_options の各ワードはビット単位で解釈されます。一部のビットには次のようなシンボリック名が与えられています。LSB はビット 0 です。
ビット | 名前 | 目的 |
---|---|---|
0 | deflate_hint | 圧縮された JAR ファイル要素を要求します |
1 | is_class_stub | このファイルにはクラスのバイトコードが保持されています |
deflate_hint (LSB) が設定されている場合、圧縮解除プログラムはその出力のサイズを小さくするよう要求されます (ただし必須ではない)。たとえば、圧縮解除プログラムで JAR ファイルを生成する場合は、JAR 要素を圧縮すればよいでしょう。#archive_options ワードの deflate_hint ビットも同じ効果を持っているため、実際には、各ファイルについてこれら 2 つのビットの論理和が取られます。is_class_stub (最下位から 2 番目のビット) が設定されている場合、リソースファイルの説明は実際にはスタブになり、ファイルの内容は Pack200 アーカイブで定義されるいずれかのクラスのバイトコードとして定義されます。ファイルオプションのほかのビットは、将来使用するために予約されており、0 でなければなりません。
転送されるファイルがクラススタブとしてマークされている場合、その内容の転送は、ファイルが空の場合と同様に行われる必要があります。つまり、file_size_hi と file_size_lo はどちらも 0 でなければならず、対応するバイトが file_bits に含まれていてはいけません。圧縮解除プログラムは、class_this などで転送されるクラスについて、クラスファイルの内容を再構成することによってリソースファイルの内容を提供する必要があります。ほかのリソースファイルと同様に、クラススタブは名前、更新時間、および deflate_hint を持つことができます。
クラススタブとクラスの順序は一致します。派生パラメータ #class_stub_count は、クラススタブとしてマークされたファイルの数と定義されます。これは file_options で設定されている is_class_stub ビットの数でもあります。したがって、#class_stub_count は #class_count 以下でなければなりません。最初のクラススタブは、最初のクラスのバイトコードを受け取るファイルを定義し、以下同様に続きます。クラススタブには名前があり、file_name で転送されますが、この名前は空の文字列の場合もあります。この場合、圧縮解除プログラムでは、クラスファイルの標準名を使用する必要があります。これは、クラスのバイトコード名に文字列「.class」を付加することによって作成されます (パッケージの区切り文字にはスラッシュ (/) を使用)。したがって、クラスファイルに標準名以外の任意の名前を付けることもできますが、通常の方法でクラスから作成した名前を付けると、最適な圧縮処理になります。
#class_count が #class_stub_count より大きい場合、圧縮解除プログラムは、最後の明示的ファイルのあとに十分な数の無意味なクラススタブが追加で転送された場合と同様に動作する必要があります。これらの無意味なスタブの名前は空の文字列で、deflate_hint や file_modtime は 0 です。
したがって、(have_file_options が 0 であるなどの理由で) クラススタブがまったくない単純な場合には、圧縮解除プログラムはファイルをその転送順序に従って生成し、次にクラスの転送順序に従って生成する必要があります。各クラスファイルには、標準名、更新時間、およびデフレーションヒントが必要です。これらは、#archive_modtime および #archive_options の deflate_hint ビットから継承されます。余分な転送サイズが必要になりますが、圧縮プログラムから圧縮解除プログラムに指示して、リソースファイルやクラスファイルを任意の固定順序で出力したり、各出力ファイルに任意の名前、更新時間、およびデフレーションヒントを指定したりすることもできます。圧縮解除プログラムは、順序が重要となる形式 (JAR アーカイブなど) で出力する場合、この順序を尊重する必要があります。
圧縮プログラムでは、特に異なる指示がない場合、ファイルの順序を任意に選択できます。類似の統計データを持つファイルが互いに隣接するように並べると、ポストパス圧縮プログラムが存在する場合はファイルの内容をまとめて (DEFLATE アルゴリズムの場合は同じウィンドウで) 処理できるようになり、有利になることがよくあります。転送効率が向上するように入力ファイルを並べ直すことのできる圧縮プログラムであれば、入力ファイルの元の順序を維持するよう強制するコマンドオプションを備えているでしょう。すべてではありませんが、一部の配備アプリケーションではこの順序が重要になるためです。
圧縮プログラムでは、クラスファイルをリソースファイルと同様に扱って転送することもできます。ビット単位で正確に維持する必要のあるクラスファイルや、圧縮プログラムでは十分な精度で圧縮して転送できないクラスファイルも、この方法を使用して転送できます。圧縮解除プログラムは、リソースファイルとしてビット単位で転送されるクラスファイルを受け入れる必要があります。
注:圧縮解除プログラムで実行される最後の処理は出力ファイルの組み立てなので、圧縮解除プログラムの実装を多少容易にするために、ファイルとその属性を制御するバンドはアーカイブの最後に転送されます。特に、リソースファイルのサイズは任意なので、そのビットをアーカイブの末尾に配置することで、圧縮解除プログラムでリソースファイル用の一時的なストレージを割り当てる必要がなくなります。
5 セットのフラグバンドでは、修飾子ビットや属性制御ビットが転送されます。ic_flags バンドでは、ネストされたクラスの修飾子ビットが転送されます。また、class_flags_lo、field_flags_lo、method_flags_lo、および code_flags_lo と、それぞれに対応するオプションの上位ワードバンド class_flags_hi、field_flags_hi、method_flags_hi、および code_flags_hi でも、修飾子ビットと属性制御ビットが転送されます。ic_flags には上位ワードはありません。これらのバンド内の各値は、32 ビット符号なしバイナリ数値として解釈されます。これらのバンド内の各ビットは、Java アクセス修飾子 (ACC_PRIVATE など) の有無、クラス属性、フィールド属性、メソッド属性、コード属性 (Deprecated や SourceFile など) の有無、または必要なほかの制御情報 (クラスファイルにデフォルト以外のバージョン番号が設定されているかどうかなど) の有無を、それぞれ独立して指定します。
クラス、フィールド、メソッド、および Code の各属性について、最大 63 ビットのフラグ値がそれぞれ class_flags_lo、field_flags_lo、method_flags_lo、code_flags_lo で転送され、オプションで class_flags_hi、field_flags_hi、method_flags_hi、code_flags_hi で転送されます。これらのフラグ値は、それぞれ class_flags、field_flags、method_flags、code_flags と呼ばれる 64 ビット数値に組み立てられます。Code 属性を除き、フラグビットの下位 16 ビットはアクセスフラグの転送に使用できます。上位 16 ビット (オプションの上位ワードも転送される場合は 47 ビット) は、定義済み属性または圧縮プログラムで定義された属性が存在するかどうかを示すために使用されます。フラグ値の 64 番目のビット位置は予約されており、転送される場合は 0 でなければなりません。
Code 属性のフラグ値はオプションで、省略されている場合は 0 と見なされます。Code 属性のフラグ値が転送されるのは、#archive_options の have_all_code_flags ビットが設定されているか、Code 属性に対応する code_headers の要素に特殊な値 0 が設定されている場合だけです。コードヘッダーの詳細については、下記を参照してください。
クラス、ネストされたクラス、フィールド、およびメソッドの場合、修飾子に対するフラグビット位置の割り当ては、クラスファイル形式で使用されるものと同じです。たとえば、Pack200 アーカイブ形式とクラスファイル形式のどちらでも、LSB は常に ACC_PUBLIC を表します。オーバーフロー属性と呼ばれる属性の有無は、フラグビットで直接示されるのではなく、別のバンドにそのインデックスが発生することによって示されます。クラス、フィールド、メソッド、およびコードの場合、マスク 0x0001000 で設定されるビット 16 は、オーバーフロー属性の有無を示します。ネストされたクラスの場合、フラグビット 16 は、外側のクラスと名前に関する明示的なフィールドの有無を示します。詳細については、下記を参照してください。
ビット | 意味 |
---|---|
0 | ACC_PUBLIC |
1 | ACC_PRIVATE |
2 | ACC_PROTECTED |
3 | ACC_STATIC |
4 | ACC_FINAL |
5 | ACC_SYNCHRONIZED (ACC_SUPER) |
6 | ACC_VOLATILE (ACC_BRIDGE*) |
7 | ACC_TRANSIENT (ACC_VARARGS*) |
8 | ACC_NATIVE |
9 | ACC_INTERFACE |
10 | ACC_ABSTRACT |
11 | ACC_STRICT |
12 | ACC_SYNTHETIC* |
13 | ACC_ANNOTATION* |
14 | ACC_ENUM* |
16 | オーバーフロー (別の場所にさらにバンドデータがある) |
特に、クラス、フィールド、およびメソッドのフラグの下位 16 ビットはクラスファイルで可視になるため、修飾子ビットを保持できます。コードフラグワードのビットはどれもクラスファイルで不可視になります。これらのフラグビットは、コードのサブ属性だけに使用されます。
これに対し、ネストされたクラスレコードには属性が含まれないため、ネストされたクラスレコードの下位 16 ビットは修飾子だけに使用されます。属性に関するこのセクションの残り部分では、ネストされたクラスレコードに関連するフラグワードは無視します。
圧縮プログラムでは、特定の属性の有無を圧縮解除プログラムに示すために、フラグ値の 63 ビットの任意の位置を割り当てることができます。圧縮プログラムでは、修飾子ビットに属性の定義を割り当てることによって、修飾子ビットを「引き継ぐ」ことができます。そのためには、そのビット位置を記述する属性の定義を発行します。
修飾子ビットかどうかにかかわらず、デフォルトで別の目的に使用されているビットを圧縮プログラムで「上書きする」場合、そのビットは元の意味を失います。ただし、圧縮プログラムは、同じビットについて (同じコンテキストで) 明示的な定義を 2 回発行することはできません。
各種類の属性は、4 つの情報によって定義されます。つまり、適用対象であるエンティティー (クラス、フィールド、メソッド、またはコード)、割り当てられているビット位置があればそのビット位置、クラスファイルに現れるとおりの属性の名前、および (クラスファイルに発生する属性を圧縮解除プログラムで正しく書式設定できるようにする) 属性のレイアウトです。圧縮プログラムで、同じ名前とレイアウトを同じコンテキストで 2 回指定することは誤りです。異なるレイアウトで同じ名前を繰り返すことや、異なる名前で同じレイアウトを繰り返すことは、誤りではありません。
最初の 2 つの項目は、1 バイトの「ヘッダー」にビット単位でエンコードされ、attr_definition_headers バンドで転送されます。最後の 2 つの項目は、attr_definition_name バンドと attr_definition_layout バンドの対応する要素で cp_Utf8 参照として転送されます。したがって、圧縮プログラムは 3 つのバンドを使用して圧縮解除プログラムに属性の型を宣言し、これらの各バンドの長さは #attr_definition_count です。
attr_definition_bands: *attr_definition_headers :BYTE1 [#attr_definition_count] *attr_definition_name :UNSIGNED5 [#attr_definition_count] (cp_Utf8) *attr_definition_layout :UNSIGNED5 [#attr_definition_count] (cp_Utf8)
属性定義のヘッダーバイトの最下位 2 ビットは、符号なしフィールドとして扱われ、属性のコンテキストタイプを指定します。コンテキストタイプとは、属性の適用対象であるエンティティーの種類です。
(h & 0x03) | コンテキストタイプ |
---|---|
0 | 属性はクラスに適用されます |
1 | 属性はフィールドに適用されます |
2 | 属性はメソッドに適用されます |
3 | 属性は Code 属性に適用されます |
属性定義のヘッダーバイトの最上位 6 ビットは、符号なしフィールドとして扱われ、属性がフラグワードのどのビット位置に割り当てられるかをオプションで指定します。
(h >> 2) | フラグビットの割り当て |
---|---|
0 | オーバーフロー属性。どのビットにも割り当てられません |
1 | 属性はビット 0 (下位ワードの LSB) に割り当てられます |
2 | 属性はビット 1 に割り当てられます |
3 | 属性はビット 2 に割り当てられます |
... | |
32 | 属性はビット 31 (下位ワードの MSB) に割り当てられます |
33 | 属性はビット 32 (上位ワードの LSB) に割り当てられます |
... | |
63 | 属性はビット 62 に割り当てられます |
(値なし) | ビット 63 (上位ワードの MSB) は 0 でなければなりません |
各クラス属性は、この仕様であらかじめ定義されている属性か圧縮プログラムで明示的に定義された属性かにかかわらず、属性インデックスと呼ばれる一意の番号を持っています。属性にフラグビットが割り当てられている場合、その属性インデックスはフラグビットの位置 (0 から 62 までの番号) と同じになります。定義済み属性には、すべてデフォルトでフラグビットが割り当てられます。
クラス属性にフラグビットが割り当てられていない場合、それはオーバーフロー属性であり、そのインデックスはクラス属性の定義順序 (つまり転送順序) に従って順番に割り当てられます。この方法で順番に割り当てられる最初のインデックスは、#have_class_flags_hi が設定されていない場合は 32 で、設定されている場合は 63 になります。これらのインデックスは、個々のクラスに発生する属性を宣言するために、アーカイブ内で使用されます。
同様に、フィールド属性、メソッド属性、およびコード属性にも、それぞれ独自の属性インデックスが割り当てられます。クラス属性のインデックスやこれらのインデックスは、互いに独立しています。したがって、属性 (つまり名前とレイアウトのペア) は、そのコンテキストタイプと属性インデックスによってアーカイブ内で一意に指定されます。フィールド属性とメソッド属性は、フラグビットに割り当てられる場合があり、それ以外の場合は 32 以上のインデックスを持つオーバーフロー属性です。ほかの属性と同様に、コード属性には明示的な数値を割り当てるか、32 から始まるインデックスを暗黙的に割り当てることができます。クラス属性の場合と同様に、#archive_options の該当するビットによって上位フラグワードのバンドが選択された場合、フィールド、メソッド、およびコードのオーバーフロー属性のインデックスは 63 以上になります。
いくつかの属性はあらかじめ定義されており、圧縮プログラムでこれらの定義を発行する必要はありません。レイアウトとフラグビットの割り当て (インデックス) は暗黙的に定義されています。
63 より小さい属性インデックスはすべての場合に使用できますが、定義済み属性に割り当てられているインデックスと競合する可能性があります。これには、Pack200 形式の将来の拡張で定義される定義済み属性 (16 から 62 までの範囲) も含まれます。現在のバージョンでは、定義済み属性のインデックスはすべて 17 から 31 までの範囲にあるため、修飾子フラグがデフォルトで上書きされることはなく、また、上位フラグワードを常に使用する必要はありません。
定義済み属性の名前とインデックス割り当てを次に示します。定義済みのレイアウトについては後述します。
索引 | コンテキストタイプ | 名前 |
---|---|---|
16 | C、F、M | (オーバーフロー属性) |
17 | クラス | SourceFile |
18 | クラス | EnclosingMethod |
19 | C、F、M | Signature |
20 | C、F、M | 非推奨 |
21 | C、F、M | RuntimeVisibleAnnotations |
22 | C、F、M | RuntimeInvisibleAnnotations |
23 | クラス | InnerClasses |
24 | クラス | "class-file version" |
17 | Field | ConstantValue |
17 | メソッド | コード |
18 | メソッド | 例外 |
23 | メソッド | RuntimeVisibleParameterAnnotations |
24 | メソッド | RuntimeInvisibleParameterAnnotations |
25 | メソッド | AnnotationDefault |
0 | コード | StackMapTable |
1 | コード | LineNumberTable |
2 | コード | LocalVariableTable |
3 | コード | LocalVariableTypeTable |
16 | コード | (オーバーフロー属性) |
ビット 16 は、クラス、フィールド、メソッド、およびコードのオーバーフロー属性の有無を示すインジケータとしてあらかじめ定義されています。エンティティーにオーバーフロー属性が存在する場合は、その数が attr_count バンドに保持され、オーバーフロー属性のレイアウトを指定する attr_indexes バンドに各オーバーフロー属性が連続した値として保持されます。このオーバーフロー属性の処理について詳しくは、下記を参照してください。
これらの定義済み属性インデックスは、属性を選択するためのビット位置だけでなく、属性データを転送するバンドの固定順序も決定します。詳細については、下記を参照してください。
クラス、フィールド、およびメソッドに関連する 5 種類のメタデータ属性 RuntimeVisibleAnnotations、RuntimeInvisibleAnnotations、RuntimeVisibleParameterAnnotations、RuntimeInvisibleParameterAnnotations、および AnnotationDefault をサポートするためのビットもあらかじめ定義されています。最後の 3 種類はメソッドだけに適用されます。これらの属性の意味と形式は、JSR 175 で定義されています。
圧縮プログラムでは、フラグワードのビット 16 以外のビットを設定せずに、すべての属性レイアウトインデックスを明示的に class_attr_indexes などのバンドで転送することもできます。圧縮解除プログラムは、どちらの方法で発生した属性インデックスでも処理できる必要があります。無意味ですが、属性の数が明示的に 0 として転送される場合もあります。圧縮プログラムでは賢明な選択を行い、可能な場合はビット 16 をクリアして、割り当てられているほかのフラグビットを設定するようにしてください。
定義済みビットはどれも、クラスファイル形式に格納される 16 ビットフラグ値の中で現在使用されているフラグビットや将来使用されるフラグビットとは干渉しません。ただし、フラグワードの下位 16 ビットは、どのファイルでも実際に設定されない場合、圧縮プログラムで自由にほかの属性に割り当てて再利用できます。
別のセクションで説明するとおり、InnerClasses 属性は特別に扱われるため、圧縮プログラムでこの属性の定義をクラスコンテキストで発行することは誤りです。Code 属性も特別に扱われます。この属性の定義をメソッドコンテキストで発行することは誤りです。
この仕様では、定義済みでない属性の有無や形式をどのように圧縮プログラムに通知するかは定義されていません。この仕様では、圧縮プログラムがこのような属性について通知されると想定し、圧縮プログラムから圧縮解除プログラムにこの情報が正しく転送されることを義務付けています。特殊なケースとして、バイトを含んでいない属性 (つまり、長さ 0 の属性) を圧縮プログラムで転送する場合に、空の文字列として既知のレイアウトを持つ属性であるかのように転送することは妥当です。
以降の説明では、クラスファイルの属性に格納されているスカラー値を圧縮解除プログラムでクラスファイルに再構成するときに、属性レイアウトの特定の要素を使用する必要がある場合は、その要素がスカラー値を管理すると呼ぶことにします。その特定のレイアウト要素がスカラー値の転送されるバンドを管理する、とも呼びます。
属性レイアウトは「小言語」の文字列で定義されます。圧縮解除プログラムは、この文字列を解析して、レイアウト要素のシーケンスに変換する必要があります。これらの各要素が、属性値の転送と格納を管理します。特に、レイアウトは定数プールの参照の位置をすべて宣言して、その値を適切な表現で (定数プールインデックスまたは何らかの数値として) 転送できるようにします。
使用できる属性レイアウトとしてもっとも単純なものは、定数プール参照の宣言のシーケンスに、ほかのすべてを管理する 1 バイトの宣言を混合したものになるでしょう。圧縮プログラムでは、このようなレイアウトを任意で使用して属性を記述できます。ただし、より具体的な属性レイアウトを使用する方が、圧縮処理は向上します。
通常、属性には定数プールの参照と小整数が含まれています。多くの場合、これらには、後続のパターンの複製を制御する整数が含まれています。定数プールの参照は、可能であれば強く型付けされます。また、null 参照に対するエンコードの提供も宣言される必要があります。フラグビットまたはバイトコードインデックスをエンコードする小整数も、そのように宣言される必要があります。それによって、これらの小整数に特別なエンコード技術を使用できるようになります。
レイアウトの宣言は、次の文法に従って構成された UTF8 文字列です。この文法は、バンド構造を記述する文法や、この仕様の別の部分に記載されているほかの文法からは独立しています。
attribute_layout: ( layout_element )* | ( callable )+ layout_element: ( integral | replication | union | call | reference ) callable: '[' body ']' body: ( layout_element )+ integral: ( unsigned_int | signed_int | bc_index | bc_offset | flag ) unsigned_int: uint_type signed_int: 'S' uint_type any_int: ( unsigned_int | signed_int ) bc_index: ( 'P' uint_type | 'PO' uint_type ) bc_offset: 'O' any_int flag: 'F' uint_type uint_type: ( 'B' | 'H' | 'I' | 'V' ) replication: 'N' uint_type '[' body ']' union: 'T' any_int (union_case)* '(' ')' '[' (body)? ']' union_case: '(' union_case_tag (',' union_case_tag)* ')' '[' (body)? ']' union_case_tag: ( numeral | numeral '-' numeral ) call: '(' numeral ')' reference: reference_type ( 'N' )? uint_type reference_type: ( constant_ref | schema_ref | utf8_ref | untyped_ref ) constant_ref: ( 'KI' | 'KJ' | 'KF' | 'KD' | 'KS' | 'KQ' ) schema_ref: ( 'RC' | 'RS' | 'RD' | 'RF' | 'RM' | 'RI' ) utf8_ref: 'RU' untyped_ref: 'RQ' numeral: '(' ('-')? (digit)+ ')' digit: ( '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' )
クラスファイルに発生する属性は、それぞれ対応するレイアウト定義に (圧縮プログラムによって) 関連付けられます。レイアウト定義は、属性のバイトの意味を正確に記述するものです。圧縮プログラムでは、各書式要素の連続する値を転送するために、書式要素ごとに独自のバンドを割り当てる必要があります。定義済み属性の場合、そのレイアウト要素に割り当てられるバンドは、この仕様で名前によって定義されています。
属性のレイアウト要素で管理される各値は、圧縮プログラムによって 32 ビット値に変換され、バンドの要素として転送されます。このバンドは、そのレイアウト要素のために一意に作成され、そのレイアウト要素によって管理されます。integral レイアウト要素の場合、その変換は単に、クラスファイルに特定の型で格納されている数値を表します。reference 要素は、ローカル定数プールの参照 (クラスファイルにとってローカル、という意味) を、アーカイブ内でグローバルな型付き参照に変換します。
同じ種類のレイアウト要素が複数ある場合、それらは別個のレイアウト要素と見なされます。また、レイアウト間でバンドが共有されることはありません。したがって、圧縮プログラムで転送される新しいレイアウト定義ごとに、独自のバンドセットが暗黙的に定義されます。以前に定義した属性レイアウトを新しいインデックスに割り当て直すと、新しいバンドセットが作成されます。以前に定義されたバンドセットが再利用されることはありません。
圧縮プログラムで新しい属性を定義する場合は、それらの属性によって管理されるバンドも作成する必要があります。圧縮プログラムはこれらのバンドを、定義済み属性のバンド文法で予約されている位置の直後 (class_attr_bands、field_attr_bands、method_attr_bands、または code_attr_bands の末尾) に転送する必要があります。これらのバンドの順序は、それらを管理している属性レイアウト要素の定義順序に対応している必要があります。このようにすると、圧縮解除プログラムで属性値を見つけることができるようになります。
同一の属性レイアウト文字列に含まれている 2 つのレイアウト要素の定義順序は、その文字列に発生する順序に対応します。同一の属性レイアウト文字列に含まれていない 2 つのレイアウト要素の定義順序は、attr_definition_layout バンドでのレイアウト定義のインデックス順序に対応します。つまり、同じコンテキストタイプのレイアウトのバンドどうしでは、インデックスの小さいものが大きいものより前になります。小さいインデックスを持つレイアウトが attr_definition_layout バンドであとに定義されている場合でも、これは当てはまります。定義済み属性によって管理されるバンドも、このような順序に従っているように見えます。ただし、定義済みのクラス属性のバンドは、ほかのすべてのクラス属性のバンドより前になります。フィールド属性、メソッド属性、コード属性についても同様です。
属性に格納される整数値のサイズは、書式文字 'B'、'H'、または 'I' によってそれぞれ 1 バイト、2 バイト、または 4 バイトに指定されます。整数型は通常は符号なしですが、接頭辞 'S' を持つものは符号付きです。この符号付けの指定により、1 バイト型または 2 バイト型を 32 ビット値に拡張する方法 (符号を拡張するか、0 を挿入するか) が決まります。また、32 ビット値の転送に使用されるプライマリエンコードも決まります。クラスファイルに格納される整数はすべて「ビッグエンディアン」なので、どの整数型書式要素も、上位ビットから先にバイトに符号化される整数を参照します。
整数型レイアウト (つまり、integral 非終端語の下にある要素) は、符号付きまたは符号なしの整数値を管理します。整数型レイアウトの宣言に 'P' 文字または 'O' 文字が含まれている場合、そのレイアウトでは特殊なプライマリエンコード BCI5 または BRANCH5 が使用されます (下記を参照)。宣言に 'S' 文字を含んでいるほかの整数型レイアウトは、プライマリエンコード SIGNED5 を使用するバンドを管理します。符号なし整数レイアウト要素とフラグレイアウト要素は、プライマリエンコード UNSIGNED5 を使用するバンドを管理します。ただし例外として、整数レイアウト 'B' (符号なしバイト) は、プライマリエンコード BYTE1 を使用するバンドを管理します。
符号付きバイト用の特別なエンコードはありません。属性に符号付きバイトフィールドが含まれている場合は、符号なしフィールドと同様に扱い、単純な 'B' レイアウト要素を割り当てることができます。'SB' レイアウトは、符号ビットがまれにしか使用されないバイトアルファベットを使って、絶対値の小さい 1 バイト整数を転送できるようにするためのものです。これは、ポストパス圧縮プログラムで Huffman コーディングを使用してバイトを表現する場合に望ましいレイアウトです。このような符号化は、一貫性のあるバイトアルファベットが圧縮プログラムに提供されたときに最適に動作するためです。
格納されたバイトコードインデックスは、レイアウト接頭辞 'P' で宣言されます。このレイアウト要素は、メソッドまたはコードのみに使用できます。バイトコードインデックスには任意の数値を格納できます。ただし、格納された数値は、バンド要素として転送される前に再設定されます。この処理は、命令境界上にないバイト位置はまれになるという前提で行われます。
BCI リナンバリングを使用すると、命令境界のインデックスをコンパクトに作成できます。また、ややコンパクトではなくなりますが、命令内のバイトのアドレスやバイトコードの境界外のバイトのアドレスなど、ほかのすべての 32 ビット整数も符号化できます。具体的には、最初の命令の最初のバイトには 0、2 番目の命令の最初のバイトには 1、というように最後の命令まで番号が付けられます。最後の命令の 1 つあとのバイト位置には、次の番号が付けられます。これは、命令の数と同じ値です。最初の複数バイト命令の 2 番目のバイトには、次の番号が付けられます。これは、命令の数より 1 だけ大きい値です。番号が付けられていない残りのバイトには、後続の番号が割り当てられます。これ以上の順序の乱れはありません。十分大きい正の数値や、負の数値に対しては、この番号再設定は恒等関数になります。
BCI リナンバリングの命令境界の位置を検出するために、_wide バイトコード (0xc4) は次の命令の一部と見なされ、その形式を調べるのに利用されます。圧縮プログラムから byte_escape (254) または ref_escape (253) という擬似命令が転送された場合、圧縮解除プログラムでは、BCI リナンバリングを計算するときに、これらの各命令によって生成されるバイトコードシーケンスを整数型の命令として受け入れる必要があります。したがって、_wide バイトコードを除く bc_codes の各バイトに命令境界が存在するほか、"aload_0_xxx" の各バリエーション ("aload_0_getstatic_this" など) にも境界が存在します。これらの擬似オペレーションコードは、バイトコード命令のペアに展開されるためです。
この概念については、「付録」セクションの「擬似コード」を参照してください。
レイアウト要素の接頭辞が 'P' で、'PO' ではない場合は、番号再設定後のバイトコードインデックスが転送されます。この番号再設定はバイトコードインデックスのリナンバリングと呼ばれます。バイトコードインデックスを保持するバンドのプライマリエンコードは BCI5 です。
この処理の例を次に示します。20 バイトのバイトコードデータと 5 つの命令を持つメソッドが位置 { 0, 4, 6, 10, 17 } にあるとします。BCI リナンバリングはこれら特定の数値をコンパクトに [0..4] に変換します。より詳細には、BCI リナンバリングは [0..20] を数値 { 0, 6, 7, 8, 1, 9, 2, 10, 11, 12, 3, 13, 14, 15, 16, 17, 18, 4, 19, 20, 5 } に変換し、[0..20] の範囲外にある値は変更しません。このメソッドの属性に 'P' レイアウト要素が割り当てられている場合で、クラスファイルに数値 6 が格納されているとき、6 は 3 番目の命令のオフセットなので、数値 2 が転送されます。
レイアウトの接頭辞が 'PO' の場合は、その前のレイアウト要素もレイアウトタイプ 'P' または 'PO' のバイトコードインデックスでなければなりません。その間に角括弧 '[' や ']' などの構造が存在してはいけません。この場合、'PO' 要素によって管理されるバンドで転送される値は、現在の 'PO' 要素で管理される番号再設定後のバイトコードインデックスと、その前の 'P' または 'PO' 要素で管理される番号再設定後のバイトコードインデックスとの差になります。前の要素が 'P'の場合、これは実際に前のバンドの対応する位置で転送される値になります。
'PO' レイアウト要素は 'P' 要素と同じ種類の属性データを管理しますが、隣接するバイトコードインデックスどうしに相関関係があることを前提としたエンコードを使用します。この番号再設定は、前の要素との差を含めて、バイトコードオフセットのリナンバリングと呼ばれます。バイトコードオフセットを保持するバンドのプライマリエンコードは BRANCH5 です。レイアウト要素に 'S' 文字または 'B' 文字が含まれている場合でも、このエンコードが使用されます。
バイトコードオフセットは、レイアウト接頭辞 'O' で宣言されます。このレイアウト要素は、前のバイトコードインデックス要素 (接頭辞が 'P' で、'PO' ではないもの) の直後に続く必要があります。このレイアウト要素で管理される値はすべてオフセットと見なされます。前に格納されている対応するバイトコードインデックスにオフセットが適用されて、別のバイトコードインデックスが生成されます。つまり、前の値も、前の値と現在の値の合計も、命令境界を参照することが想定されています。'PO' レイアウトと同様に、転送される値は、番号再設定後の 2 つのバイトコードインデックスの差になります。'PO' レイアウトによって管理されるバンドは、バイトコードオフセットを保持し、プライマリエンコード BRANCH5 を使用します。'PO' レイアウトとは異なり、'O' レイアウトによって管理される格納された値は、2 つのバイトコードインデックスの差になります。
したがって、'O' レイアウトと 'PO' レイアウトの両方によって管理されるバンドは、バイトコードオフセットを保持し、BRANCH5 を使用してこれらのオフセットをエンコードします。ただし、'P' レイアウトと 'PO' レイアウトの両方によって管理される属性値は、絶対的なバイトコードインデックスを格納します。格納されたバイトコードオフセットを管理するのは 'O' レイアウトだけです。格納されたオフセットは、'S' 文字が存在する場合を除き、クラスファイルでは符号なしフィールドとして扱われます。これに対し、格納されたインデックスは、常に符号なしフィールドとして扱われます。
前述の例で、20 バイトのメソッドの属性に 'P' 要素に続いて 'PO' レイアウト要素も割り当てられている場合で、クラスファイルに数値 20 が格納されているとき、転送される数値は、20 を 5 に再設定してから、前に転送された数値を引いた (5-2) として求められます。したがって、数値 3 が転送されることになります。一方、格納されている数値が 7 の場合 (位置 6 にある命令内の BCI)、転送される数値は (10-2)、つまり 8 になります。'PO' ではなく 'O' レイアウト要素によって管理されている場合は、転送される値が同じく 3 と 8 であっても、20 と 7 ではなく、格納された値 14 (20-6) と 1 (7-6) に対応することになります。
接頭辞 'F' を持つフラグ要素は、算術値ではなく短いビット配列をエンコードすることを想定した整数値をエンコードします。ビットをアクセス修飾子として解釈する必要はありません。フラグレイアウト要素を転送するバンドのプライマリエンコードは UNSIGNED5 です。ただし例外として、'FB' レイアウトはプライマリエンコード BYTE1 を使用します。
書式文字 'B'、'H'、または 'I' の代わりに 'V' で転送される整数型の値は、クラスファイル属性内で領域をまったく占有しません。これらのレイアウト要素を使用すると、圧縮解除プログラムで、クラスファイル属性に直接現れることのない個数やタグを使用して転送を制御できます。たとえば、属性がバイト配列であり、その配列は自己サイズ指定型ではなく、クラスファイルの属性ヘッダーに記述されたバイト数を満たすように拡張する場合を考えます。このような属性にレイアウト 'NV[B]' が指定されているとします。'V' レイアウト要素としてどの値を転送するかは、圧縮プログラムが決定するとします。このような決定には、この仕様には含まれていない属性の形式に関する詳細情報を使用する必要があるでしょう。すべての場合において、圧縮解除プログラムはこれらの値を尊重する必要がありますが (個数やタグとして使用されているとき)、クラスファイルに格納してはいけません。
複製は接頭辞 'N' で始まり、複製数と呼ばれる整数要素と、複製本体と呼ばれる角括弧で囲まれた一連の要素が続きます。このレイアウトによって管理される属性データは、複製数とそれに続くデータ配列で構成されます。この配列の要素数は複製数と同じです。各配列要素は、複製本体内のレイアウトによって管理されます。
複製数のレイアウト要素は、複製数を転送するバンドを管理します。このバンドのプライマリエンコードは UNSIGNED5 (レイアウトが 'NB' で始まる場合は BYTE1) です。対応する複製本体によって管理されるバンドのサイズは、複製数のバンドで転送される値を合計することによって求めることができます。
共用体は接頭辞 'T' で始まり、共用体タグと呼ばれる整数要素と、共用体ケースと呼ばれる角括弧で囲まれた一連のラベル付き要素が続きます。数値の桁数は任意ですが、その算術値はバンド値と比較される前に 32 ビット整数に切り詰められます。バンド値のサイズも 32 ビットです。最後のラベル以外の各ラベルは、丸括弧で囲まれた 1 つ以上の (場合によっては符号付きの) 10 進数で構成されます。これらは共用体タグと呼ばれます。デフォルトのケースとなる最後のラベルは、空の丸括弧のペアでなければなりません。また、どの共用体にも同じケースタグを 2 つ含めることはできません。このレイアウトによって管理される属性データは、整数型のタグ値とそれに続くデータで構成されます。このデータの形式はタグによって決まります。タグに続くデータは、タグの値に一致するラベルを持つ (一意の) 共用体ケースか、デフォルトのケースによって管理されます。各共用体ケースによって管理されるバンドのサイズは、タグのレイアウトで管理されるバンドに含まれている値の数をカウントすることによって求めることができます。単純な整数型バンドと同様に、共用体タグバンドのプライマリエンコードは SIGNED5、BYTE1、または UNSIGNED5 です。これは、レイアウトに 'S' 文字が含まれているか、レイアウトが 'TB' か、またはそれ以外かに応じて決まります。
共用体タグでは、ハイフンで区切られた 2 つの数値は、これらの数値も含めた範囲を指定します。2 番目の数値は最初の数値より大きくなくてはなりません。最初の数値から 2 番目の数値まで、これらの数値も含めたすべての数値のリストを表す省略形です。レイアウトは、省略形を完全なリストで置き換えた場合とまったく同様に扱われます。
属性内の定数プールの参照は強く型付けされ、アーカイブのいずれかの定数プールのインデックスとして転送される場合があります。'KI'、'KJ'、'KF'、'KD'、および 'KS' で始まるレイアウトタイプは、integer、long、float、double、および string の各型の定数に対して、ローカル定数プールに格納されたインデックスを管理する必要があります。同様に、'RC'、'RS'、'RD'、'RF'、'RM'、'RI'、および 'RU' で始まるレイアウトタイプは、クラス、シグニチャー、記述子 (名前と型のペア)、フィールド参照、メソッド参照、インタフェースメソッド参照、および UTF8 文字列の各型のシンボルに対して、ローカル定数プールに格納されたインデックスを管理する必要があります。これらの参照はすべて、対応するグローバル定数プールの 32 ビットインデックスとして、プライマリエンコード UNSIGNED5 を使用するバンドで転送されます。レイアウトに 'B' が含まれている場合でも、これは当てはまります。下記の表を参照してください。
シグニチャー参照 (レイアウト 'RS') は、クラスファイル内では Utf8 参照 (レイアウト 'RU') と同一ですが、アーカイブファイルでの符号化方式は異なります。cp_Utf8 定数プールと cp_Signature 定数プールはそれぞれ独立したインデックスを持ち、異なる方法で転送されるためです。
'KQ' で始まるレイアウト要素は、フィールドに関連する属性にのみ発生する可能性があります。これらは、フィールドのシグニチャーによって指定される定数プール内の定数を参照します。このレイアウト要素が役立つのは、定義済みの ConstantValue 属性に使用する場合だけでしょう。次の表に、'KQ' レイアウトに使用できる正当なフィールドシグニチャーと、その格納された参照が含まれている対応する定数プールを示します。
フィールドシグニチャー | 'KQ' 定数プール |
---|---|
B | cp_Int |
S | cp_Int |
C | cp_Int |
Z | cp_Int |
I | cp_Int |
J | cp_Long |
F | cp_Float |
D | cp_Double |
Ljava/lang/String; | cp_String |
Ljava/lang/Class; | cp_Class |
定数プール参照を強く型付けできない場合、圧縮プログラムでは untyped_ref ('RQ') を使用する必要があります。このような型指定のない参照のエンコードは、すべてのプールに対するインデックスであり、Pack200 アーカイブに発生する順序に従って番号が再設定されます。このように自然な発生順序に従って並べられたすべての定数のシーケンスは cp_All と呼ばれます。たとえば、cp_Utf8 プールのすべての要素に、'RQ' レイアウトと 'RU' レイアウトで同様に番号が付けられます。一方、型指定のない参照に対して、cp_Int プールの最初の要素 (要素 0) には、cp_Utf8_count という番号が付けられます。また、型指定のない参照に対して、cp_Float プールの最初の要素には、cp_Utf8_count+cp_Int_count という番号が付けられます。
予期しない型の定数プールインデックスを含んでいる属性が見つかった場合、圧縮プログラムは、その属性を転送することを拒否するか、強く型付けされた要素の代わりに 'RQN' 要素を使って緩やかなレイアウト定義で転送することを選択できます。圧縮解除プログラムは、圧縮プログラムから転送される正当なレイアウト定義をすべて尊重する必要があります。それによって不正な書式のクラスファイルが生成される場合でも、これは当てはまります。極端な場合、圧縮プログラムはクラスファイルをリソースファイルと同様に扱って転送することもできます。この場合、クラス固有の圧縮は行われませんが、例外的な書式の属性がビット単位で正確に維持されます。
reference レイアウトタイプに文字 'N' が含まれている場合、定数プールエントリをエンコードするバンド値はすべて 1 増加し、null 値は 0 としてエンコードされます。それ以外の場合、null 値は負の 1 (-1) としてエンコードされます。
上記の規則がバンドのプライマリエンコードに与える効果は、レイアウト要素ごとに優先される規則のリストとしてまとめることができます。その中で最初に該当する規則によって、エンコードが決まります。
次の表は、各種レイアウト要素のバンドとエンコードをまとめたものです。(型と整数サイズの可能な組み合わせがすべて示されているわけではありません。)
レイアウト 要素 |
格納される 値 |
転送される 値 |
プライマリ エンコード |
---|---|---|---|
B | u1 | x | BYTE1 |
FB | u1 | x | BYTE1 |
SB | u1 | (byte)x | SIGNED5 |
H | u2 | x | UNSIGNED5 |
FH | u2 | x | UNSIGNED5 |
SH | u2 | (short)x | SIGNED5 |
I | u4 | x | UNSIGNED5 |
FI | u4 | x | UNSIGNED5 |
SI | u4 | x | SIGNED5 |
PH | u2 | renumber_bci(x) | BCI5 |
POH | u2 | renumber_bci(x) - renumber_bci(x0) | BRANCH5 |
OH | u2 | renumber_bci(x0+x) - renumber_bci(x0) | BRANCH5 |
NB[...] | u1 | x (サイズカウントとしても機能) | BYTE1 |
NH[...] | u2 | x (サイズカウントとしても機能) | UNSIGNED5 |
NI[...] | u4 | x (サイズカウントとしても機能) | UNSIGNED5 |
TB... | u1 | x | BYTE1 |
TSB... | u1 | (byte)x | SIGNED5 |
TH... | u2 | x | UNSIGNED5 |
TSH... | u2 | (short)x | SIGNED5 |
KIB | u1 | indexOf(lcp[x], cp_Int) | UNSIGNED5 |
KIH | u2 | indexOf(lcp[x], cp_Int) | UNSIGNED5 |
KII | u4 | indexOf(lcp[x], cp_Int) | UNSIGNED5 |
KINH | u2 | 1+indexOf(lcp[x], cp_Int) | UNSIGNED5 |
KJH | u2 | indexOf(lcp[x], cp_Long) | UNSIGNED5 |
KFH | u2 | indexOf(lcp[x], cp_Float) | UNSIGNED5 |
KDH | u2 | indexOf(lcp[x], cp_Double) | UNSIGNED5 |
KSH | u2 | indexOf(lcp[x], cp_String) | UNSIGNED5 |
KQH | u2 | indexOf(lcp[x], cp_fieldSpecific) | UNSIGNED5 |
RCH | u2 | indexOf(lcp[x], cp_Class) | UNSIGNED5 |
RSH | u2 | indexOf(lcp[x], cp_Signature) | UNSIGNED5 |
RDH | u2 | indexOf(lcp[x], cp_Descr) | UNSIGNED5 |
RFH | u2 | indexOf(lcp[x], cp_Field) | UNSIGNED5 |
RMH | u2 | indexOf(lcp[x], cp_Method) | UNSIGNED5 |
RIH | u2 | indexOf(lcp[x], cp_Imethod) | UNSIGNED5 |
RUH | u2 | indexOf(lcp[x], cp_Utf8) | UNSIGNED5 |
RQH | u2 | indexOf(lcp[x], cp_All) | UNSIGNED5 |
RQNH | u2 | 1+indexOf(lcp[x], cp_All) | UNSIGNED5 |
RQNI | u4 | 1+indexOf(lcp[x], cp_All) | UNSIGNED5 |
ここで、変数 X は、レイアウト要素によって管理される、属性に格納された値を表します。変数 X0 は、'P' で始まる直前のレイアウト要素によって管理される、格納された値を表します。式 renumber_bci(I) は、命令境界の参照を短縮するためにバイトコードインデックス I を再設定することを表します (前述の説明を参照)。式 lcp[I] は、インデックス I でのローカル定数プールの参照を表します。または、I が 0 の場合は、特殊な null 値を表します。
式 indexOf(lcp[I], cp) は、Pack200 のグローバル定数プール cp での、定数 lcp[I] のインデックス (0 から始まる) を表します。この定数は cp に適した型であると想定しています。lcp[I] が特殊な null 値を持つ場合、この式の値は -1 になります。
null 参照は常に、参照を保持するバンドで転送できます。null を受け入れるよう指定されているバンドでは値 0、それ以外のバンドでは値 -1 を転送します。UNSIGNED5 エンコードでは、-1 を 5 バイトで表現できます。
名前 cp_All は、Pack200 のすべてのグローバル定数プールをその転送順序に従って連結することによって作成される配列を表します (前述の説明を参照)。名前 cp_fieldSpecific は、包含するフィールドのシグニチャーによって選択されるグローバル定数プールを表します (前述の説明を参照)。
call レイアウト要素は、丸括弧で囲まれた符号付き 10 進数 N です。これは、この呼び出しが出現する呼び出し可能レイアウトを基準にして、レイアウト仕様のトップレベルの構造体内で N 番目の呼び出し可能レイアウト (callable) を表します。呼び出しが呼び出し可能レイアウトの外部に出現することは不正です。この呼び出し可能レイアウトのことを、呼び出しの呼び出し先と呼ぶことにします。
たとえば、レイアウト要素 '(2)' は、この呼び出しが出現する呼び出し可能レイアウトより 2 つあとの呼び出し可能レイアウトを呼び出します。どの呼び出し可能レイアウトにも、一致する呼び出し先が存在する必要があります。レイアウト内で最後の呼び出し可能レイアウトには、'(1)' という呼び出しが発生してはいけません。同様に、最初の呼び出し可能レイアウトには、'(-1)' という呼び出しが発生してはいけません。なお、'(0)' という自己呼び出しは常に正当です。
call レイアウト要素は、それが呼び出す呼び出し可能レイアウト要素によって直接管理される属性データを、間接的に管理します。クラスファイル形式に関するかぎり、呼び出し可能レイアウトの本体に含まれているテキストで呼び出しを置き換えた場合と同様の効果になります。
ただし、この置換セマンティクスでは、バンド構造に対する呼び出しレイアウト要素の効果は記述されません。呼び出しレイアウト要素は、バンドを直接管理することはありません。代わりに、間接的に管理しているデータを、呼び出し先によってより直接的に管理されるバンドで転送するように指定します。
呼び出し先が呼び出し自体よりあとに記載されている場合、その呼び出しは順方向呼び出しです。順方向呼び出しの効果として、その呼び出しや同じ呼び出し先に対するほかの順方向呼び出しで、呼び出し先のバンドを共有できるようになります。たとえば、次のレイアウト仕様には 2 つのバンドがあり、後者のバンドは、どちらかの共用体ケースによって間接的に管理されるデータを転送します。デフォルトの共用体ケースはどのバンドも管理しないため、ASCII 文字 'A' または 'B' 以外のタグバイトには、それに続くバイトはありません。読みやすくなるように、このレイアウトには空白が追加されています。
[TB (65) [(1)] (66) [(1)] ( ) [] ] [H]
呼び出し先の記載が呼び出し自体より前に始まる場合もあります。このような呼び出しは逆方向呼び出しと呼ばれ、'(0)' または '(-N)' という形式で記述する必要があります。その呼び出し先は逆方向呼び出し可能レイアウトと呼ばれます。呼び出し可能レイアウトは、どの呼び出しの対象にもなっていないものや順方向呼び出しだけの対象になっているものを除き、すべて逆方向呼び出し可能レイアウトです。逆方向呼び出しと逆方向呼び出し可能レイアウトによって、再帰やループが可能になります。N 分木の例を次に示します。その葉は Utf8 文字列で、先頭に 0 バイトが付加されています。内部ノードはツリーノードの配列で、先頭に 1 バイトが付加されています。この例では、呼び出し先に呼び出しが含まれており、直接再帰が行われています。レイアウトは、要素 'TB'、'NH'、および 'RUH' で 3 つのバンドを管理しています。
[TB (1) [NH[ (0) ]] (0) [RUH] ]
このような相互に再帰的なレイアウトによって管理されるバンドのサイズは、逆方向呼び出しの明示的な個数を利用して求められます。この個数は、レイアウトの属性バンドの前に、class_attr_calls バンドまたは類似の 3 つのバンドで転送されます。この詳細については後述します。
コンテキストタイプ | 名前 | レイアウト定義 |
---|---|---|
クラス | "class-file version" | (空) *(注を参照) |
クラス | InnerClasses | (空) *(注を参照) |
クラス | EnclosingMethod | RCHRDNH |
クラス | SourceFile | RUNH *(注を参照) |
クラス | Signature | RSH |
クラス | (メタデータ) | (次を参照) |
クラス | 非推奨 | (空) |
Field | ConstantValue | KQH |
Field | Signature | RSH |
Field | (メタデータ) | (次を参照) |
Field | 非推奨 | (空) |
メソッド | コード | (空) *(注を参照) |
メソッド | 例外 | NH[RCH] |
メソッド | Signature | RSH |
メソッド | (メタデータ) | (次を参照) |
メソッド | 非推奨 | (空) |
コード | StackMapTable | (次を参照) |
コード | LineNumberTable | NH[PHH] |
コード | LocalVariableTable | NH[PHOHRUHRSHH] |
コード | LocalVariableTypeTable | NH[PHOHRUHRSHH] |
"class-file version"、InnerClasses、SourceFile、および Code のレイアウト定義に含まれているアスタリスク (*) は、これらの属性には特別な処理が加えられることを示しています。クラスに関する "class-file version" 擬似属性は、書式 VV を使用した場合と同様に転送されますが、圧縮解除プログラムは結果をクラスファイルの属性に格納するのではなく、クラスファイルのヘッダーに格納します。詳細については、下記を参照してください。クラスに関する InnerClasses 属性は、書式 NV[RCVTV[(0)[]()[RCNVRUNV]]] を使用した場合と同様に部分的に転送されますが、圧縮解除プログラムは、(おそらく) InnerClasses 属性を発行する前に、受け取った値を処理します。詳細については、下記を参照してください。定義済みレイアウトを使用して SourceFile 属性を転送する場合、特別な規則により、そのデフォルト値は明確な標準文字列になります。最後に、メソッドに関する Code 属性は、code_bands で転送されます。
[NH[(1)]] [TB (64-127) [(2)] (247) [(1)(2)] (248-251) [(1)] (252) [(1)(2)] (253) [(1)(2)(2)] (254) [(1)(2)(2)(2)] (255) [(1)NH[(2)]NH[(2)]] () [] ] [H] [TB (7) [RCH] (8) [PH] () [] ]このレイアウトの仕様を、StackMapTable 属性を定義するクラスファイル形式の仕様と比較することにより、次のことが観察できます。2 番目の呼び出し可能レイアウトは、クラスファイル形式の仕様に基づく stack_map_frame 構造を表します。2 番目の呼び出し可能レイアウトの共用体内部の各ケースは、stack_map_frame 共用体メンバー same_locals_1_stack_item_frame、same_locals_1_stack_item_extended、chop_frame (および same_frame_extended)、append_frame (3 レイアウトの共用体ケース用)、full_frame、(デフォルトレイアウトの共用体ケース内の) same_frame をそれぞれ表します。3 番目および 4 番目の呼び出し可能レイアウトは、クラスファイル形式の仕様に基づく offset_delta 値および verification_type_info 構造を表します。
[NH[(1)]] [RSH NH[RUH(1)]] [TB (66,67,73,83,90) [KIH] (68) [KDH] (70) [KFH] (74) [KJH] (99) [RSH] (101) [RSH RUH] (115) [RUH] (91) [NH[(0)]] (64) [RSH [RUH(0)]] () [] ]このレイアウトは、可視の注釈と不可視の注釈の両方で使用されます。2 番目の呼び出し可能レイアウトでは、メタデータ仕様に基づく注釈構造が記述されています。3 番目の呼び出し可能レイアウトでは、メタデータ仕様に基づく member_value 構造が記述されています。この最後の呼び出し可能レイアウトは、メソッドの AnnotationDefault 属性に対して単独で使用されます。メソッドパラメータの注釈 (可視および不可視) も、このレイアウトを使用して事前に定義されます。その際、パラメータの数をカウントする呼び出し可能レイアウトが付加されます。
[NB[(1)]] [NH[(1)]] [RSH NH[RUH(1)]] [TB...]
この規則は多数の Java コンパイラの歴史的な動作に合致しており、この規則を使用することにより、圧縮プログラムが派生した「明白な」SourceFile 名に Utf8 文字列を割り当てることを回避できます。圧縮プログラムが、真の null 参照を持つ不規則な SourceFile 属性を転送する必要がある場合、非標準のレイアウトを使用しなければなりません。次に、クラス名および対応する派生 SourceFile 名の例を示します。
クラス | SourceFile |
---|---|
foo | foo.java |
foo/bar | bar.java |
foo/bar$baz | bar.java |
foo/bar#baz#1 | bar.java |
foo.bar.baz#1 | baz.java |
ic_bands: *ic_this_class :UDELTA5 [#ic_count] (cp_Class) *ic_flags :UNSIGNED5 [#ic_count] *ic_outer_class :DELTA5 [COUNT(1<<16,...)] (null or cp_Class) *ic_name :DELTA5 [LENGTH(*ic_outer_class)] (null or cp_Utf8)これら 4 つのタプルは、定数プールのエントリと同様、グローバルに共有されます。この仕様では、これらは、クラスファイル形式で異なる順序で格納されるとしても、<C,F,C2,N> と従来の方法で記述されます。グローバルに定義されたこれら 4 つのタプルをまとめて ic_All と呼びます。通常、アーカイブ内の個別のクラスからネストされたクラスのレコードへの明示的なリンケージは存在しません。その代わり、Pack200 アーカイブからクラスファイルを抽出する際に、ネストされたクラスのレコードのサブセットを選択できます。抽出されるクラスファイルの定数プールに実際に記述されているすべてのネストされたクラスを記述するには、これで十分です。抽出するクラスファイルを X とすると、このサブセットは ic_Relevant(X) になります。これは、ic_All の X に対する関連サブセットです。関連サブセットの選択アルゴリズムについては、あとで説明します。オプションで、圧縮プログラムは、ローカルの InnerClasses 属性を転送することにより、指定した任意のクラスに対して、その関連サブセットへの調整を行えます。これについても、後述のクラス属性に関するセクションで説明します。
ic_this_class および ic_flags バンドは、どちらも長さが #ic_count です。また、これらのバンドの対応する要素は、タプルごとに、ネストされたクラスの識別情報 (cp_Class 参照で表される) およびフラグビットマスクを指定します。
ネストされたクラスのフラグビットの位置 16 (マスク 0x00010000 内で設定される) は、アーカイブファイル内部にあり、ic_outer_class および ic_name バンド内にタプルに対応するエントリが存在するかどうかを示します。このため、これら両方のバンドの長さは、位置 16 のすべてのフラグビットの合計になります。通常、このビットの設定、および外部フィールドと名前フィールドの明示的な指定が必要なのは、ネストされたクラスの数パーセントだけです。
タプルが ic_outer_class および ic_name バンド内にエントリを保持する場合、これらのバンドはその外部クラスと単純名を指定します。これらは、null になり得る cp_Class 参照および null になり得る cp_Utf8 参照としてそれぞれ表されます。それ以外の場合、タプルの外部クラスおよび外部名は予測されていると言われます。この場合は、ネストされたクラス自体の名前の綴りを解析することで正確に予測可能であるはずです。
ネストされたクラスは、バイトコード名を持ちます。バイトコード名により、クラスファイル内部のクラスに名前が付けられます。この名前の綴りは「分解名」とも呼ばれ、追加の句読記号が含まれます。また、数字や含まれるクラスの名前が含まれることもあります。バイトコード名を外部クラスやクラス名に解析可能であり、このクラスおよび名前がネストされたクラスの真の外部クラスおよびクラス名と同一である場合、外部クラスおよびクラス名は予測可能であると言います。
予測可能な外部クラスおよびクラス名を抽出する際、ネストされたクラス名の綴りに適用される、クラスのバイトコード名に関する次の文法に従う必要があります。これは、バンド構造を管理する文法、および本仕様のほかの箇所で説明する文法とは別個の文法です。終端の DOLLAR は、コードが 0x2D 以下の任意の文字 (「$」、「#」など) を参照します。終端の SLASH は、スラッシュ「/」(コード 0x2E) またはドット文字「.」(コード 0x2F) を参照します。終端の DIGIT は、ASCII 10 進数 (0x30 - 0x39 の 10 個の文字コードのいずれか) を参照します。終端の LETTER は、その他の任意の文字を参照します。つまり、コードが 0x3A 以上の任意の文字を参照します。
bcn: (bcnCase1 | bcnCase2 | bcnCase3 | bcnCase4) bcnCase1: packageQual (namePart)? DOLLAR number bcnCase2: packageQual (namePart)? DOLLAR number DOLLAR predictableICName bcnCase3: predictableOuter DOLLAR predictableICName bcnCase4: packageQual (namePart)? predictableOuter: packageQual namePart predictableICName: LETTER (LETTER | DIGIT)* namePart: (LETTER | DIGIT | DOLLAR)+ number: (DIGIT)+ packageQual: (namePart SLASH)*
この文法では、任意のクラス名があいまいな仕方で複数の部分に分割されます。これには、オプションの接頭辞 predictedOuter、オプションの接尾辞 predictedICName、およびオプションの数値接尾辞を含められます。あいまいさは、bcn 非終端用の代替ケースを指定の順序で優先することにより、解決する必要があります。たとえば、bcnCase1 が一致する場合、その他の 1 つまたは両方が一致するとしてもこれが使用されます。
predictableICName 非終端が解析された場合、予測可能なネストされたクラス名はこれに対応する文字列になります。それ以外の場合、予測可能なネストされたクラス名は、null と解釈されます。
predictableOuter 外部非終端が解析される場合、対応する文字列は予測可能な外部クラス名になります。それ以外の場合、予測可能な外部クラス参照は、null と解釈されます。number 非終端が解析される場合、predictableOuter は解析できません。次に、予測、非予測、および誤予測の例を示します。
ネストされたクラス: | java/util/Map のメンバー Entry |
---|---|
分解名: | java/util/Map$Entry |
外部, 名: | java/util/Map, Entry |
予測可能かどうか: | 可能 (Map.Entry は Map のメンバーであるため) |
ネストされたクラス: | anonymous |
分解名: | java/util/AbstractList$1 |
外部, 名: | (なし), (なし) |
予測可能かどうか: | 可能 (ic_name は null であるため) |
ネストされたクラス: | Local という名前の非メンバー |
分解名: | java/util/AbstractList$2$Local |
外部, 名: | (なし), Local |
予測可能かどうか: | 可能 (ic_name は Local であるため) |
ネストされたクラス: | Local という名前の非メンバー |
分解名: | java/util/AbstractList#2#Local |
外部, 名: | (なし), Local |
予測可能かどうか: | 可能 (ic_name は Local であるため) |
ネストされたクラス: | Foo のメンバー $2$Local |
分解名: | Foo$$2$Local |
外部, 名: | (なし), Local |
予測可能かどうか: | 不可 (外部名がなく、ic_name が誤予測される) |
ネストされたクラス: | クラス Red$Herring |
分解名: | Red$Herring |
外部, 名: | Red, Herring |
予測可能かどうか: | 不可 (ic_name Herring が予測される) |
ネストされたクラス: | X$1 のメンバー Q |
分解名: | X$1$Q |
外部, 名: | (なし), Q |
予測可能かどうか: | 不可 (Q は X$1 のメンバーであるため) |
ネストされたクラス: | X$Y のメンバー Z |
分解名: | X$Y$Z |
外部, 名: | X$Y, Z |
予測可能かどうか: | 可能 (X$Y.Z は X$Y のメンバーであるため) |
ネストされたクラス: | X のメンバー Y$Z |
分解名: | X$Y$Z |
外部, 名: | X$Y, Z |
予測可能かどうか: | 不可 (外部名および ic_name が誤予測される) |
圧縮解除プログラムは、「予測可能」のマークが付けられたネストされたクラス名を処理する際に、ネストされたクラスのバイトコード名を解析して、包含しているクラス、オプションの数値、およびオプションの名前に分ける必要があります。圧縮プログラムが外部クラスおよび名前を常に明示的に指定することも可能です。この場合、圧縮解除プログラムがネストされたクラス名の解析を行う必要は一切なくなります。ただし、圧縮解除プログラムはこの解析をいつでも実行できる準備ができていなければなりません。
圧縮プログラムが、予測された名前や外部クラスに対して cp_Utf8 または cp_Class 内のエントリを転送しなかった場合は、圧縮解除プログラムは該当する定数を内部で作成する必要があります。これは、たとえ定数転送シーケンス cp_All ではない場合でも、cp_Utf8 または cp_Class エントリとして参照されます。ただし、圧縮プログラムが予測された名前または外部クラスと同じ綴りの定数を転送した場合、圧縮解除プログラムは新しい定数を作成せずに、その定数を使用する必要があります。圧縮解除プログラム内で作成されるこの種の内部定数は、出力ファイルの定数プールに特殊な順序で挿入されるため、出力ファイル内で検出できます。(後述の出力順序の説明を参照)。
この概念については、「付録」セクションの「擬似コード」を参照してください。
圧縮解除プログラムが ic_this_class、ic_flags、ic_name、および ic_outer_class バンドを受信して、必要な名前解析を実行すると、4 タプルの ic_All セットが作成されます。このセットは、圧縮解除プログラムにより個別のクラスファイル用に合成される InnerClasses 属性のプライマリソースになります。
class_bands: *class_this :DELTA5 [#class_count] (cp_Class) *class_super :DELTA5 [#class_count] (cp_Class) *class_interface_count [#class_count] :DELTA5 *class_interface :DELTA5 [SUM(*class_interface_count)] (cp_Class) *class_field_count :DELTA5 [#class_count] *class_method_count :DELTA5 [#class_count] *field_descr :DELTA5 [SUM(*class_field_count)] (cp_Descr) field_attr_bands *method_descr :MDELTA5 [SUM(*class_method_count)] (cp_Descr) method_attr_bands class_attr_bands code_bands
class_this、class_super、class_flags_lo、class_flags_hi (存在する場合)、class_interface_count、class_field_count、および class_method_count バンドはすべて、長さ #class_count です。次に、これらのバンドの対応する要素により、各クラスの名前とスーパークラス、実装されたインタフェースの数、宣言されたフィールドの数、および宣言されたメソッドの数が転送されます。前述の定義のとおり、各クラスのフラグワード内のアクセス修飾子ビットと属性指示子が組み合わせられて、class_flags_lo 内で転送されます。
class_interface バンドには、クラスインタフェース宣言の実行 (複数回)、class_interface_count の各要素の実行 (1 回)、および対応するクラスへの適用が含まれます。
class_this、class_super、および class_interface の各要素は、定数プール cp_Class への参照 (実際は null 以外の参照) です。
java/lang/Object のある特殊なケースでは、格納されるスーパークラスは null 参照である必要があります。class_super バンドのほかの要素の転送を一切妨げることなく、圧縮プログラムが、null のスーパークラス参照に遭遇したとき、その位置に現在のクラス参照の複製を転送するという規則を使用します。クラスがそれ自体から継承することは不正であるため、null 参照のリナンバリングは明快なものになります。
field_descr バンドには、class_field_count の各要素に対応する 1 回の要素実行が含まれます。また、これは対応するクラス内の連続するフィールドに適用されます。method_descr バンドには、class_method_count の各要素に対応する 1 回の要素実行が含まれます。また、これは対応するクラス内の連続するメソッドに適用されます。
クラスファイル形式とは異なり、フィールドおよびメソッド記述子は、名前参照と型参照のペアとしてではなく、「名前と型」定数プールへの単一の参照として格納されます。
method_descr バンドが備えるプライマリエンコードでは、メソッド参照の大半がソートされている場合に、最高のパフォーマンスが得られます。圧縮プログラムは、各クラス内でメソッドをソートすることにより、この利点を享受できます。(注:圧縮プログラムが最高の圧縮率を実現するためにメソッドを並べ替えることは、一般に受け入れられています。ただし、フィールドの順序はリフレクションを通じて明らかであり、直列化などの一部の機能では重要であるため、フィールドを並べ替えてはいけません。
属性バンドは、文法で指定された位置に配置されます。それらについて、このあと説明します。属性バンドに含まれるフラグバンドにより、対応するクラス、フィールド、メソッドのアクセス修飾子と属性指示子の両方が伝えられます。
Pack200 アーカイブは、メソッドコードブロック専用のバンドおよびバイトコード自体で終わります。メソッドにコード属性が含まれる場合、圧縮プログラムはコード属性を、割り当て先のフラグビット (6 番目の LSB) 内に記述するか、オーバーフロー属性 (インデックス 6) として記述する必要があります。Code 属性のフィールドは、code_bands 非終端内に編成されるバンド内で転送されます。
各 Code 属性の仕様は、スタックスロットの数、ローカルスロットの数、およびハンドラの数を宣言する 3 つのキーパラメータで始まります。#Stack は、コードにより使用されるスタックスロットの数です。#NALocal は、コードが使用する引数のないローカルの数です。クラスファイル内で宣言されるローカルの実際の数は、#NALocal に、メソッドパラメータにより要求されるローカルの数を加えた値になります。これは、メソッドの署名により決定されます。#Handler は、例外ハンドラの数です。
code_headers バンドに含まれる一連のバイトはそれぞれ、コード属性の最初の 3 つのキーパラメータを簡潔にエンコードします。ゼロ以外の 255 の各バイト値は、3 組で一意となる #NALocal、#Stack、および #Handler をエンコードします。転送される各 Code 属性のコードヘッダーは (当然) 1 つです。
特殊コードヘッダーバイトゼロ (0x00) は、code_max_stack、code_max_na_locals、および code_handler_count バンド内にコード属性の 3 つのパラメータが存在することを示します。コードヘッダーバイトがゼロ以外の場合、これら 3 つのバンドは対応する Code 属性のエントリを転送しません。
code_flags_lo バンドは、Code 属性のコードヘッダーバイトがゼロである、have_all_code_flags ビットが #archive_options ワードで設定されている、の一方または両方が真の場合にのみ、対応する Code 属性のエントリを転送します。Code 属性が転送された code_flags_lo 値を持たない場合、そのフラグワードはゼロになり、サブ属性を持ちません。code_flags_hi バンドは、code_flags_lo 値が前述の方法で転送され、また、#have_code_flags_hi も設定されている場合にのみ、対応するエントリを転送します。
ゼロ以外のコードヘッダーバイトが表すコード属性パラメータは、次のスキームに従います。
ヘッダー範囲 | #Stack | #NALocal | #Handler |
---|---|---|---|
1 <= x <= 144 | (x-1) % 12 | (x-1) / 12 | 0 |
145 <= x <= 208 | (x-145) % 8 | (x-145) / 8 | 1 |
209 <= x <= 255 | (x-209) % 7 | (x-209) / 7 | 2 |
x = 0 | code_max_stack | code_max_na_locals | code_handler_count |
例外ハンドラカウントが 1 バイトコードヘッダーでエンコードされるかどうか、また code_handler_count の明示的な要素であるかどうかに関係なく、これらのバンドが属性レイアウト 'NV[PHPOHPOHRCNH]' により管理されているかのように、コード属性ごとに code_handler_start_P、code_handler_end_PO、code_handler_catch_PO、および code_handler_class_RCN 内の値の実行が各ハンドラに 1 つ存在します。'NV' レイアウト要素自体が管理するバンドは存在しません。これはハンドラカウントです。
つまり、Handler.start、Handler.end、Handler.catch 値は 3 つで 1 組となり、組ごとに (renumber_bci により) リナンバリングされて転送されます。また、Handler.end が、そのリナンバリングと同じトリプレット内の Handler.start のリナンバリングの差として転送され、Handler.catch が、そのリナンバリングと同じトリプレット内の Handler.end のリナンバリングの差として転送されます。最後に、cp_Class への増分参照として code_handler_class_RCN が転送されます。ハンドラクラス参照 null の場合にはゼロが転送されます。
code_bands: *code_headers :BYTE1 [COUNT(Code,...)] *code_max_stack :UNSIGNED5 [COUNT(0,*code_headers)] *code_max_na_locals :UNSIGNED5 [COUNT(0,*code_headers)] *code_handler_count :UNSIGNED5 [COUNT(0,*code_headers)] *code_handler_start_P :BCI5 [SUM(*code_handler_count)] *code_handler_end_PO :BRANCH5 [SUM(*code_handler_count)] *code_handler_catch_PO :BRANCH5 [SUM(*code_handler_count)] *code_handler_class_RCN :UNSIGNED5 [SUM(*code_handler_count)] (null or cp_Class) code_attr_bands
圧縮プログラムがクラス (フィールド、メソッド、またはコード) にオーバーフロー属性があると指定する場合、class_attr_count バンド内の対応する要素 (それぞれ、field_attr_count、method_attr_count、または code_attr_count バンド) を転送する必要があります。次に、このカウントは、クラス (フィールド、メソッド、またはコード) の属性を管理する属性レイアウトのインデックスである class_attr_indexes (それぞれ、field_attr_indexes、method_attr_indexes、または code_attr_indexes) 内の実行サイズを指定します。圧縮プログラムは、各オーバーフロー属性の属性レイアウト定義インデックスを転送する必要があります。
圧縮プログラムは、class_flags の要素内のビットまたは class_attr_indexes の要素により選択される属性レイアウトごとに、レイアウト要素が管理するデータをクラス属性から取得して、このデータをエンコードする要素をレイアウトが管理するバンド内で転送する必要があります。フィールド、メソッド、およびコード属性にも同じ条件が適用されます。
圧縮プログラムは、データを転送し、逆方向呼び出しを含む属性レイアウトごとに、圧縮解除プログラムが属性レイアウトを順番に処理する際、各逆方向呼び出し可能レイアウトが逆方向呼び出しの対象になる回数を転送する必要があります。これらの呼び出しカウントは、適用先のレイアウトのコンテキスト型に応じて、class_attr_calls、field_attr_calls、method_attr_calls、および code_attr_calls バンド内で転送されます。呼び出しカウントは、逆方向呼び出しレイアウトごとに 1 つずつ、呼び出し可能レイアウトの定義順に転送されます。(上述を参照)。呼び出しカウントの転送は、1 回以上使用されるレイアウト内部で発生する逆方向呼び出し可能レイアウトに対してのみ実行されます。呼び出しカウントは、呼び出し可能レイアウトに対するエントリについては順方向呼び出しであるために一切カウントしません。また、呼び出し可能レイアウトに対する初期呼び出し (属性処理を開始する) もカウントしません。使用しているレイアウトで逆方向呼び出し可能レイアウトが出現しているが、偶然その逆方向呼び出し可能レイアウトに到達しなかった場合、呼び出しカウントがゼロになることがあります。これらの呼び出しカウントは、相互再帰的なレイアウトのバンドのサイズ設定に固有の循環を中断するために必要です。これらは、バンド値をさまざまな出力クラスに配布する前に、圧縮解除プログラムがアーカイブ内の全バンドを検出するのに必要な最小情報を提供します。このため、呼び出しカウントは、レイアウトごとに (そのレイアウトの属性発行がすべて要約されて) 提供されます。
圧縮解除プログラムは、圧縮プログラムにより転送された明示的な属性レイアウト定義の処理をすべて担当します。圧縮解除プログラムは、これらのレイアウトにより管理される追加バンドを受信する準備ができている必要があります。これは、レイアウト定義のインデックスを読み取る際、各追加バンドで転送される値の正確な数を読み取る準備ができている必要があります。
この仕様で定義されるバンドの文法には、すべての定義済み属性レイアウトで管理されるバンドが含まれます。明瞭性を高めるため、一部のバンド名にはそれを作成したレイアウト要素の一部が含まれます。Deprecated 属性はレイアウトが空であるため、この属性にはバンドが存在しません。
「Synthetic」という名前の属性は、以前のバージョンの Java の標準クラスファイル形式ではありますが、最近のバージョンの Java では新規フラグビット (ACC_SYNTHETIC, 0x1000) で置き換えられているため、この仕様では直接サポートされていません。ただし、圧縮解除プログラムが処理できるよう、未使用のフラグビットを割り当てて、明示的なゼロ長のレイアウト定義を発行することにより、Synthetic 属性、およびここで事前に定義されていないほかのゼロ長の属性すべてを、圧縮プログラムが処理することが推奨されています。
圧縮プログラムが新規レイアウトの定義を選択する場合、これらのレイアウトにより管理されるバンドが、定義済みレイアウトのバンドの直後に追加されます。定義済み属性バンドの順序および構造は、圧縮プログラムがそれらを明示的に定義したかのように、通常の方法による定義済みレイアウト定義を反映します。
class_attr_bands: *class_flags_hi :UNSIGNED5 [#class_count*#have_class_flags_hi] *class_flags_lo :UNSIGNED5 [#class_count] *class_attr_count :UNSIGNED5 [COUNT(1<<16,...)] *class_attr_indexes :UNSIGNED5 [SUM(*class_attr_count)] *class_attr_calls :UNSIGNED5 [...] *class_SourceFile_RUN :UNSIGNED5 [COUNT(SourceFile,...)] (null or cp_Utf8) *class_EnclosingMethod_RC :UNSIGNED5 [COUNT(EnclosingMethod,...)] (cp_Class) *class_EnclosingMethod_RDN :UNSIGNED5 [COUNT(EnclosingMethod,...)] (null or cp_Descr) *class_Signature_RS :UNSIGNED5 [COUNT(Signature,..)] (cp_Signature) class_metadata_bands ic_local_bands *class_file_version_minor_H :UNSIGNED5 [COUNT(version,...)] *class_file_version_major_H :UNSIGNED5 [COUNT(version,...)] field_attr_bands: *field_flags_hi :UNSIGNED5 [SUM(*class_field_count)*#have_field_flags_hi] *field_flags_lo :UNSIGNED5 [SUM(*class_field_count)] *field_attr_count :UNSIGNED5 [COUNT(1<<16,...)] *field_attr_indexes :UNSIGNED5 [SUM(*field_attr_count)] *field_attr_calls :UNSIGNED5 [...] *field_ConstantValue_KQ :UNSIGNED5 [COUNT(ConstantValue,...)] (cp_Int, etc.; see note) *field_Signature_RS :UNSIGNED5 [COUNT(Signature,...)] (cp_Signature) field_metadata_bands method_attr_bands: *method_flags_hi :UNSIGNED5 [SUM(*class_method_count)*#have_method_flags_hi] *method_flags_lo :UNSIGNED5 [SUM(*class_method_count)] *method_attr_count :UNSIGNED5 [COUNT(1<<16,...)] *method_attr_indexes :UNSIGNED5 [SUM(*method_attr_count)] *method_attr_calls :UNSIGNED5 [...] *method_Exceptions_N :UNSIGNED5 [COUNT(Exceptions,...)] *method_Exceptions_RC :UNSIGNED5 [SUM(*method_Exceptions_N)] (cp_Class) *method_Signature_RS :UNSIGNED5 [COUNT(Signature,...)] (cp_Signature) method_metadata_bands code_attr_bands: *code_flags_hi :UNSIGNED5 [...*#have_code_flags_hi] *code_flags_lo :UNSIGNED5 [...] *code_attr_count :UNSIGNED5 [COUNT(1<<16,...)] *code_attr_indexes :UNSIGNED5 [SUM(*code_attr_count)] *code_attr_calls :UNSIGNED5 [...] *code_StackMapTable_N :UNSIGNED5 [COUNT(StackMapTable,...)] *code_StackMapTable_frame_T :BYTE1 [SUM(*code_StackMapTable_N)] *code_StackMapTable_local_N :UNSIGNED5 [COUNT(255,*code_StackMapTable_frame_T)] *code_StackMapTable_stack_N :UNSIGNED5 [COUNT(255,*code_StackMapTable_frame_T)] *code_StackMapTable_offset :UNSIGNED5 [...] *code_StackMapTable_T :BYTE1 [...] *code_StackMapTable_RC :UNSIGNED5 [COUNT(7,*code_StackMapTable_T)] *code_StackMapTable_P :BCI5 [COUNT(8,*code_StackMapTable_T)] *code_LineNumberTable_N :UNSIGNED5 [...] *code_LineNumberTable_bci_P :BCI5 [...] *code_LineNumberTable_line :UNSIGNED5 [...] *code_LocalVariableTable_N :UNSIGNED5 [...] *code_LocalVariableTable_bci_P :BCI5 [...] *code_LocalVariableTable_span_O :BRANCH5 [...] *code_LocalVariableTable_name_RU :UNSIGNED5 [...] (cp_Utf8) *code_LocalVariableTable_type_RS :UNSIGNED5 [...] (cp_Signature) *code_LocalVariableTable_slot :UNSIGNED5 [...] *code_LocalVariableTypeTable_N :UNSIGNED5 [...] *code_LocalVariableTypeTable_bci_P :BCI5 [...] *code_LocalVariableTypeTable_span_O :BRANCH5 [...] *code_LocalVariableTypeTable_name_RU :UNSIGNED5 [...] (cp_Utf8) *code_LocalVariableTypeTable_type_RS :UNSIGNED5 [...] (cp_Signature) *code_LocalVariableTypeTable_slot :UNSIGNED5 [...]
クラスは、そのクラスファイルのマイナーバージョンとメジャーバージョンが、それぞれ #default_class_minver、#default_class_majver と異なる場合、"class-file version" の擬似属性を所有します。この擬似属性は、クラスのファイルのメジャーおよびマイナーバージョン番号を指定する 16 ビット整数のペアです。これらの整数は、属性レコード内ではなく、クラスのファイルのヘッダー内に格納されます。クラスファイルの規約どおり、マイナーバージョン番号が最初に配置されます。このため、マイナーバージョン番号がバンド class_file_version_minor_H 内で、メジャーバージョン番号が class_file_version_major_H 内でそれぞれ転送されます。圧縮解除プログラムは、仕様で処理対象とされているものよりも大きいマイナーまたはメジャーバージョン番号で、アーカイブを処理する必要はありません。圧縮解除プログラムは、同じメジャーバージョン番号、または同じかより小さいマイナーバージョン番号でアーカイブを処理する必要があります。
すべてのローカル InnerClasses 属性の 4 タプルは、次の 5 つのバンド内で転送されます。
ic_local_bands: *class_InnerClasses_N :UNSIGNED5 [COUNT(InnerClasses,...)] *class_InnerClasses_RC :UNSIGNED5 [SUM(*class_InnerClasses_N)] (cp_Class) *class_InnerClasses_F :UNSIGNED5 [SUM(*class_InnerClasses_N)] *class_InnerClasses_outer_RCN :UNSIGNED5 [COUNT(!=0,*class_InnerClasses_F)] (null or cp_Class) *class_InnerClasses_name_RUN :UNSIGNED5 [COUNT(!=0,*class_InnerClasses_F)] (null or cp_Utf8)
これらの属性バンド内で転送される 4 タプル <C,F,C2,N> は、それぞれローカル IC タプルと呼ばれます。クラスファイル形式はこれらの 4 タプルの要素を異なる順序 (C,C2,N,F) で格納します。指定のクラスファイルが X の場合、ローカル IC タプルのシーケンスは ic_Local(X) と呼ばれます。
各ローカル IC タプルで、圧縮プログラムは少なくとも C 値および F 値を転送します。転送されるフラグ値 F がゼロでない場合、対応する転送される定数プール参照 C2 および N も存在します。全体として、転送されるローカルタプルは、ic_All からのグローバルタプルと等しい場合も、等しくない場合もあります。
転送するローカル IC タプルが ic_All のメンバーと等価であり、ic_All のほかのメンバーが同じクラス C を定義しない場合、圧縮プログラムは、省略表記として、C およびフラグ値ゼロだけを転送できます。この場合、圧縮解除プログラムは、すべての 4 タプルコンポーネントが明示的に転送された場合とまったく同様に動作する必要があります。
ローカルタプルの 4 コンポーネントがすべて転送され、転送されるフラグ値が事実上ゼロの場合、フラグとしてゼロではなく値 0x00010000 を転送する必要があります。
多くの場合、クラスの InnerClasses 属性内に格納される 4 タプルのセットは ic_Relevant(X) のままで調整不要であるため、圧縮プログラムはローカル IC タプルを一切転送する必要がありません。付加的な 4 タプル (最小限の定数プールで必要とされるもの以外) が入力クラスファイル内で検出された場合、追加の InnerClasses エントリ用のローカル「ルート」を提供するために、一部のローカル IC タプルが (ゼロフラグ付きで) 必要になります。これは、コンパイラが、署名だけに記述されたクラスの InnerClasses エントリを必要とする場合に発生する可能性があります。圧縮プログラムは、この種の付加的なローカルルートが必要なクラスを予測し、予期しない 4 タプルだけを転送する必要があります。
class_metadata_bands: class_RVA_bands class_RIA_bands field_metadata_bands: field_RVA_bands field_RIA_bands method_metadata_bands: method_RVA_bands method_RIA_bands method_RVPA_bands method_RIPA_bands method_AD_bands class_RVA_bands: *class_RVA_anno_N :UNSIGNED5 [...] *class_RVA_type_RS :UNSIGNED5 [...] (cp_Signature) *class_RVA_pair_N :UNSIGNED5 [...] *class_RVA_name_RU :UNSIGNED5 [...] (cp_Utf8) *class_RVA_T :BYTE1 [...] *class_RVA_caseI_KI :UNSIGNED5 [...] (cp_Int) *class_RVA_caseD_KD :UNSIGNED5 [...] (cp_Double) *class_RVA_caseF_KF :UNSIGNED5 [...] (cp_Float) *class_RVA_caseJ_KJ :UNSIGNED5 [...] (cp_Long) *class_RVA_casec_RS :UNSIGNED5 [...] (cp_Signature) *class_RVA_caseet_RS :UNSIGNED5 [...] (cp_Signature) *class_RVA_caseec_RU :UNSIGNED5 [...] (cp_Utf8) *class_RVA_cases_RU :UNSIGNED5 [...] (cp_Utf8) *class_RVA_casearray_N :UNSIGNED5 [...] *class_RVA_nesttype_RS :UNSIGNED5 [...] (cp_Signature) *class_RVA_nestpair_N :UNSIGNED5 [...] *class_RVA_nestname_RU :UNSIGNED5 [...] (cp_Utf8) class_RIA_bands: *class_RIA_anno_N :UNSIGNED5 [...] (analogous to class_RVA_bands) field_RVA_bands: *field_RVA_anno_N :UNSIGNED5 [...] (analogous to class_RVA_bands) field_RIA_bands: *field_RIA_anno_N :UNSIGNED5 [...] (analogous to field_RVA_bands) method_RVA_bands: *method_RVA_anno_N :UNSIGNED5 [...] (analogous to class_RVA_bands) method_RIA_bands: *method_RIA_anno_N :UNSIGNED5 [...] (analogous to method_RIA_bands) method_RVPA_bands: *method_RVPA_param_NB :BYTE1 [...] *method_RVPA_anno_N :UNSIGNED5 [...] (analogous to method_RVA_bands) method_RIPA_bands: *method_RIPA_param_NB :BYTE1 [...] *method_RIPA_anno_N :UNSIGNED5 [...] (analogous to method_RVPA_bands) method_AD_bands *method_AD_T :BYTE1 [...] *method_AD_caseI_KI :UNSIGNED5 [...] (cp_Int) *method_AD_caseD_KD :UNSIGNED5 [...] (cp_Double) *method_AD_caseF_KF :UNSIGNED5 [...] (cp_Float) *method_AD_caseJ_KJ :UNSIGNED5 [...] (cp_Long) *method_AD_casec_RS :UNSIGNED5 [...] (cp_Signature) *method_AD_caseet_RS :UNSIGNED5 [...] (cp_Signature) *method_AD_caseec_RU :UNSIGNED5 [...] (cp_Utf8) *method_AD_cases_RU :UNSIGNED5 [...] (cp_Utf8) *method_AD_casearray_N :UNSIGNED5 [...] *method_AD_nesttype_RS :UNSIGNED5 [...] (cp_Signature) *method_AD_nestpair_N :UNSIGNED5 [...] *method_AD_nestname_RU :UNSIGNED5 [...] (cp_Utf8)
メタデータレイアウトを理解する助けとして、JSR 175 に準拠するクラスファイルで使用される各メソッドメタデータバンドについて簡単に説明します。クラスおよびフィールドメタデータバンドは、同様の方法で動作します。
JSR 175 では、クラスへの参照が必要な場合でも、CONSTANT_Class エントリへの埋め込み参照が許可されないことに留意してください。その代わり、JSR 175 では、クラスへの参照をフィールド署名としてエンコードする必要があります。これらの名前は「L」と「;」で囲む必要があります。Pack200 アーカイブは、この種の値すべてを、cp_Class ではなく cp_Signature への参照として転送します。
パラメータ注釈属性を使用するたびに、method_RVPA_param_NB バンドまたは method_RIPA_param_NB バンド (不可視の注釈用) は、パラメータカウントを表す符号なしのバイトを転送します。非パラメータ注釈属性を使用するたびに、method_RVA_anno_N バンドまたは method_RIA_anno_N バンド (不可視の注釈用) は、注釈カウントを転送します。同様に、注釈パラメータごとに、method_RVPA_anno_N バンドまたは method_RIPA_anno_N バンド (不可視の注釈用) は注釈カウントを転送します。
可視の非パラメータ注釈ごとに、method_RVA_type_RS バンドは注釈の型を転送し、method_RVA_pair_N バンドは注釈のメンバーと値のペアの数を転送します。不可視の注釈およびパラメータ注釈の類似値は、バンド method_RIA_type_RS、method_RIA_pair_N、method_RVPA_type_RS、method_RVPA_pair_N、method_RIPA_type_RS、および method_RIPA_pair_N 内で転送されます。可視の非パラメータ注釈の直接部分として転送されるメンバーと値のペアごとに、method_RVA_name_RU バンドはメンバー名を転送します。不可視の注釈およびパラメータ注釈の類似値は、バンド method_RIA_name_RU、method_RVPA_name_RU、および method_RIPA_name_RU 内で転送されます。
可視の非パラメータ注釈とともに転送される各値では、method_RVA_T バンドにより注釈値の形式を決めるバイトが転送され、類似の値がバンド method_RIA_T、method_RVPA_T、method_RIPA_T、および method_AD_T (注釈のデフォルト) 内で転送されます。これは、直接的にも、入れ子の値または注釈を介して間接的にも実行されます。これらの値タグバンドの直接使用は、対応する先行のペアカウントバンド (method_RVA_pair_N など) の値を合計してカウントされます。このカウントには、対応する後続の入れ子のペアカウントバンド (method_RVA_nestpair_N など)、および入れ子の配列長バンド (method_RVA_casearray_N など) の合計も含まれます。
後者の合計は、属性レイアウト内の逆方向呼び出しから得られるため、圧縮解除プログラムから直接計算することはできません。ただし、その合計は、method_attr_calls の要素として、圧縮プログラムによりレポートされます。各メタデータレイアウト内の最後の呼び出し可能レイアウトは、内部自体からの 2 つの逆方向呼び出しのターゲットです。そのバンドには、method_RVA_T、method_RIA_T、method_RVPA_T、method_RIPA_T、method_AD_T に対するこれらの逆方向呼び出しのカウントが、この順序で含まれます。メソッドメタデータ属性が発行されない場合、対応する逆方向呼び出しは省略されます。このため、最大 5 つの逆方向呼び出しカウントがメソッドメタデータ用に転送されます。アーカイブ内に圧縮プログラムにより定義された再帰レイアウトが存在する場合、その逆方向呼び出しカウントは method_attr_calls 内のメタデータのカウントに準拠します。
「B」、「C」、「I」、「S」、「Z」の各値タグに、method_RVA_caseI_KI 内で転送される対応する要素が存在します。この場合、値は cp_Int 参照として転送されます。不可視注釈、パラメータ注釈、および注釈のデフォルト内部の類似した整数参照が、バンド method_RIA_caseI_KI、method_RVPA_caseI_KI、method_RIPA_caseI_KI、および method_AD_caseI_KI 内で転送されます。「e」の値タグごとに、バンド method_RVA_caseet_RS および method_RVA_caseec_RU が、クラスの署名および列挙定数のメンバー名を cp_Signature および cp_Utf8 への参照として転送します。値タグ「[」ごとに、バンド method_RVA_casearray_N が入れ子の値配列の長さを転送します。値タグ「@」ごとに、バンド method_RVA_nesttype_RS および method_RVA_nestpair_N が、入れ子の注釈のクラス署名およびそのペアの数を転送します。この種の入れ子注釈のペアごとに、バンド method_RVA_nestname_RU がペアの名前を転送します。
次の表に示すバンドは、特殊なタグ文字が出現するたびに、適切な型の定数プール参照を転送します。
タグ | バンド | 参照 |
---|---|---|
'B'、'C'、'I'、'S'、'Z' | method_RVA_caseI_KI | cp_Int |
'D' | method_RVA_caseD_KD | cp_Double |
'F' | method_RVA_caseF_KF | cp_Float |
'J' | method_RVA_caseJ_KJ | cp_Long |
'c' | method_RVA_casec_RS | cp_Signature |
'e' | method_RVA_caseet_RS method_RVA_caseec_RU |
cp_Signature cp_Utf8 |
's' | method_RVA_cases_RU | cp_Utf8 |
類似のバンドグループは、ほかの 4 つのメソッド注釈型の内部、およびクラスとフィールドの可視注釈と不可視注釈の内部で、値を転送します。
次に、バイトコード命令を転送するバンドを示します。
bc_bands: *bc_codes :BYTE1 [...] *bc_case_count :UNSIGNED5 [COUNT(switch,*bc_codes)] *bc_case_value :DELTA5 [...] *bc_byte :BYTE1 [...] *bc_short :DELTA5 [...] *bc_local :UNSIGNED5 [...] *bc_label :BRANCH5 [...] *bc_intref :DELTA5 [...] (cp_Int) *bc_floatref :DELTA5 [...] (cp_Float) *bc_longref :DELTA5 [...] (cp_Long) *bc_doubleref :DELTA5 [...] (cp_Double) *bc_stringref :DELTA5 [...] (cp_String) *bc_classref :UNSIGNED5 [...] (current class or cp_Class) *bc_fieldref :DELTA5 [...] (cp_Field) *bc_methodref :UNSIGNED5 [...] (cp_Method) *bc_imethodref :DELTA5 [...] (cp_Imethod) *bc_thisfield :UNSIGNED5 [...] (cp_Field, only for current class) *bc_superfield :UNSIGNED5 [...] (cp_Field, only for current super) *bc_thismethod :UNSIGNED5 [...] (cp_Method, only for current class) *bc_supermethod :UNSIGNED5 [...] (cp_Method, only for current super) *bc_initref :UNSIGNED5 [...] (cp_Field, only for most recent new) *bc_escref :UNSIGNED5 [...] (cp_All) *bc_escrefsize :UNSIGNED5 [...] *bc_escsize :UNSIGNED5 [...] *bc_escbyte :BYTE1 [...]
バイトコード命令をより効率的に転送するため、一部の命令を転送形式に書き換えることが可能です。ldc、ldc_w、および ldc2_w バイトコードは、圧縮プログラムにより、強く型付けされた操作に書き換える必要があります。以降の記述を参照してください。一部の getstatic、putstatic、getfield、putfield、invokevirtual、invokespecial、および invokestatic バイトコードは、(圧縮プログラムのオプションを使って) より特殊で簡潔な形式に書き換えることができます。これは、コンテキスト依存であるため、オペランドの選択範囲がより限定的になります。
どんな場合でも、各命令 (おそらく書き換えられた) の最初のバイトは、bc_codes 内で転送されるバイトにより決定されます。すべてのオペランドバイトは、オペランドに復号化され、エンコードされたオペランドの型に従って別個のバンド内で転送されます。switch 命令のパディングバイト、および invokeinterface 命令の最後の 2 バイトは、圧縮解除プログラムによる再構築が可能であるため、破棄されます。
「ワイドな」接頭辞バイトコードが、bc_codes 内で転送されます。接頭辞に続く命令はワイド形式で復号化されますが、転送方法 (iinc の場合を除く) およびバンドは通常の (非ワイド) 形式と同じです。iinc 命令のワイド形式では、2 番目のオペランドの転送に bc_byte ではなく bc_short が使用されます。
すべての branch 命令は、bc_label バンド内でそのターゲット (switch が複数の場合は複数のターゲット) を転送します。これは、branch ターゲットのリナンバリングされた BCI と branch 自体のリナンバリングされた BCI との差としてエンコードされます。すでに説明したように、リナンバリングにより、最初の命令に 0、2 番目の命令に 1、という具合に番号付けが行われます。リナンバリングされた BCI 間の差は非常に小さいものです。BRANCH5 のプライマリエンコードも、たいていの場合この種の差が正であることを利用します (大半のブランチは順方向ブランチであるため)。
bc_classref バンドは、増分インデックスを cp_Class 定数プール内に伝送します。増分 (一単位ずつ増加) ではコードゼロが予約されています。コードゼロは、常に現在のクラスを参照します。これにより、自己参照である大半の共通クラス参照用のコンパクトな形式が提供されます。
bc_byte および bc_short バンドは、固定サイズのオペランドを伝送します。これらのオペランドは、命令が、元来の定義どおり、特別に圧縮された転送形式になっているため、一般の 32 ビット整数としては転送されません。これらのバンドの整数値は、セマンティクスとしてはオペランドとして符号付けされている場合でも、符号なしとして扱われます。bc_byte バンドは、multianewarray および非ワイド iinc 命令の最後のオペランドとして、また bipush および newarray 命令のオペランドとして使用されます。bc_short バンドは、ワイド iinc 命令の最後のオペランドとして、また sipush 命令のオペランドとして使用されます。
バンド bc_intref、bc_floatref、bc_longref、bc_doubleref、bc_stringref、bc_fieldref、bc_methodref、および bc_imethodref は、通常のインデックスを定数プール cp_Int、cp_Float、cp_Long、cp_Double、cp_String、cp_Field、cp_Method、および cp_Imethod 内にそれぞれ転送します。
すべての lookupswitch および tableswitch 命令で、ケースの数が bc_case_count 内で転送され、デフォルトラベルが bc_label 内で転送されます。lookupswitch の各ケース値およびケースターゲットは、bc_case_value および bc_label 内でそれぞれ転送されます。tableswitch の初期ケース値は、bc_case_value 内で転送され、その各ケースターゲットは bc_label 内で転送されます。
命令の最初のバイトが範囲 [202..255] 内のコードであるか、命令のオペランドをこの仕様の要件に従って構文解析できない場合、それは非標準の命令です。非標準命令の各バイトは、圧縮解除プログラムがそれを文字どおりに受け入れるように、特殊な包み (エンベロープ) に入れて転送する必要があります。エンベロープは、bc_code バンド内の一連の「byte_escape」および「ref_escape」操作コード、bc_escsize および bc_escrefsize 内の対応するサイズ、bc_escbyte 内のバイト、および bc_escref 内の定数プール参照で構成されます。非標準命令内の各定数プール参照は、bc_escref バンド内でエスケープおよび転送する必要があります。各 ref_escape は、この種の定数プール参照を 1 つ、最大 4 バイトまでラップします。転送される参照は cp_All 内へのインデックスであり、ゼロが最初の CONSTANT_Utf8 定数を参照し、1 が 2 番目の CONSTANT_Utf8 定数を参照する、という具合になります。
エスケープ 操作コード |
サイズ オペランド |
オペランド サイズ |
サイズ バンド |
データ バンド |
操作コード 値 |
---|---|---|---|---|---|
byte_escape | N<=255 | N バイト | bc_escsize | bc_escbyte | 254 |
ref_escape | N<=2 | N バイト | bc_escrefsize | bc_escref | 253 |
Pack200 形式は定数プール参照以外の命令バイトをすべて正確に保存するため、非標準命令内部のほかのオペランド型を、特殊な方法で転送する必要はありません。この仕様は、圧縮プログラムが非標準命令の存在および形式に対応する方法を示すものではありません。圧縮解除プログラムが、これらを正確に復号化することを求めているだけです。
次の表に、特別に書き換えられた命令の転送形式を示します。
元の 命令 |
オペランド | 転送 命令 |
書き換えが 必要か |
操作コード |
---|---|---|---|---|
ldc | cp_String[i] | aldc | はい | 18 (=ldc) |
ldc | cp_Class[i] | cldc | はい | 233 |
ldc | cp_Int[i] | ildc | はい | 234 |
ldc | cp_Float[i] | fldc | はい | 235 |
ldc_w | cp_String[i] | aldc_w | はい | 19 (=ldc_w) |
ldc_w | cp_Class[i] | cldc_w | はい | 236 |
ldc_w | cp_Int[i] | ildc_w | はい | 237 |
ldc_w | cp_Float[i] | fldc_w | はい | 238 |
ldc2_w | cp_Long[i] | lldc2_w | はい | 20 (=ldc2_w) |
ldc2_w | cp_Double[i] | dldc2_w | はい | 239 |
getstatic | (このクラスメンバー) | getstatic_this | いいえ | 202 |
putstatic | (このクラスメンバー) | putstatic_this | いいえ | 203 |
getfield | (このクラスメンバー) | getfield_this | いいえ | 204 |
putfield | (このクラスメンバー) | putfield_this | いいえ | 205 |
invokevirtual | (このクラスメンバー) | invokevirtual_this | いいえ | 206 |
invokespecial | (このクラスメンバー) | invokespecial_this | いいえ | 207 |
invokestatic | (このクラスメンバー) | invokestatic_this | いいえ | 208 |
aload_0; getstatic | (このクラスメンバー) | aload_0_getstatic_this | いいえ | 209 |
aload_0; putstatic | (このクラスメンバー) | aload_0_putstatic_this | いいえ | 210 |
aload_0; getfield | (このクラスメンバー) | aload_0_getfield_this | いいえ | 211 |
aload_0; putfield | (このクラスメンバー) | aload_0_putfield_this | いいえ | 212 |
aload_0; invokevirtual | (このクラスメンバー) | aload_0_invokevirtual_this | いいえ | 213 |
aload_0; invokespecial | (このクラスメンバー) | aload_0_invokespecial_this | いいえ | 214 |
aload_0; invokestatic | (このクラスメンバー) | aload_0_invokestatic_this | いいえ | 215 |
getstatic | (スーパークラスメンバー) | getstatic_super | いいえ | 216 |
putstatic | (スーパークラスメンバー) | putstatic_super | いいえ | 217 |
getfield | (スーパークラスメンバー) | getfield_super | いいえ | 218 |
putfield | (スーパークラスメンバー) | putfield_super | いいえ | 219 |
invokevirtual | (スーパークラスメンバー) | invokevirtual_super | いいえ | 220 |
invokespecial | (スーパークラスメンバー) | invokespecial_super | いいえ | 221 |
invokestatic | (スーパークラスメンバー) | invokestatic_super | いいえ | 222 |
aload_0; getstatic | (スーパークラスメンバー) | aload_0_getstatic_super | いいえ | 223 |
aload_0; putstatic | (スーパークラスメンバー) | aload_0_putstatic_super | いいえ | 224 |
aload_0; getfield | (スーパークラスメンバー) | aload_0_getfield_super | いいえ | 225 |
aload_0; putfield | (スーパークラスメンバー) | aload_0_putfield_super | いいえ | 226 |
aload_0; invokevirtual | (スーパークラスメンバー) | aload_0_invokevirtual_super | いいえ | 227 |
aload_0; invokespecial | (スーパークラスメンバー) | aload_0_invokespecial_super | いいえ | 228 |
aload_0; invokestatic | (スーパークラスメンバー) | aload_0_invokestatic_super | いいえ | 229 |
invokespecial | (このクラス <init>) | invokespecial_this_init | いいえ | 230 |
invokespecial | (スーパークラス <init>) | invokespecial_super_init | いいえ | 231 |
invokespecial | (新規クラス <init>) | invokespecial_new_init | いいえ | 232 |
すべてのバイトコード命令は、現在のクラスと呼ばれるクラスに含められます。現在のクラスのスーパークラス (存在する場合) は、現在のスーパークラスと呼ばれます。テキストとしてもっとも近い「新規」命令 (同一メソッド内) のオペランドは、現在の新規クラスと呼ばれます。
命令が現在のクラス内のフィールドまたはメソッドを参照する場合、それを (圧縮プログラムのオプションで) 書き換えて、「_this」という綴りの対応する操作コードとして転送することが可能です。同様に、現在のスーパークラス内のフィールドまたはメソッドを参照する命令は、「_super」という綴りの対応する操作コードとして書き換えることが可能です。どちらの場合も、直前の命令が aload_0 (操作コード 42) である場合、圧縮プログラムを使ってその命令の転送を抑制し、代わりに「aload_0_」という綴りの対応する命令コードを選択できます。それ以外の場合、「aload_0_」の派生形は選択できません。
invokespecial 命令が現在のクラス、現在のスーパークラス、または現在の新規クラス内の <init> という名前のメソッドを参照する場合、圧縮プログラムはそれぞれ invokespecial_this_init、invokespecial_super_init、invokespecial_new_init として書き換えることができます。
「_this」という綴り (「_init」ではない) の書き換えられた命令のフィールド (またはメソッド) オペランドは、特殊なバンド bc_thisfield (メソッドの場合は bc_thismethod) 内で転送されます。これらのオペランドの番号付けは、cp_Field (メソッドの場合は cp_Method) 内の一連の記号を取得して、現在のクラスのメンバーだけを選択することにより定義されます。生成されるサブセットは、順番を変更することなく、ゼロを先頭としてリナンバリングされます。これにより、現在のクラスのメンバーに小さな整数値をコンパクトにマッピングできます。
同様に、「_super」という綴りの (「_init」ではない) 書き換えられた命令のオペランドは、bc_superfield または bc_supermethod バンド内で転送され、cp_Field または cp_Method のサブセットとしてリナンバリングされます。選択されるのは、現在のスーパークラスのメンバーだけです。
最後に、「_init」という綴りの書き換えられた命令のオペランドは、バンド bc_initref 内で転送され、cp_Method のサブセットとしてリナンバリングされ、適切なクラス (現在のクラス、現在のスーパークラス、現在の新規クラス) に合わせて選択され、また、名前 <init> を持つように選択されます。通常、転送されるインデックスは非常に小さいものです。実際のところ、インデックスは呼び出されたメソッドの署名だけを選択し、大半のクラスはいくつかのコンストラクタのみを保持します。
次の表に、複数バイトの命令転送の概要を示します。
命令 | オペランド | 転送される 値 |
バンド |
---|---|---|---|
bipush | (byte) x | x & 0xFF | bc_byte |
sipush | (short) x | x & 0xFFFF | bc_short |
ildc | cp_Int[i] | i | bc_intref |
fldc | cp_Float[i] | i | bc_floatref |
aldc | cp_String[i] | i | bc_stringref |
cldc | 現在のクラス | 0 | bc_classref |
cldc | cp_Class[i] | i+1 | bc_classref |
ildc_w | cp_Int[i] | i | bc_intref |
fldc_w | cp_Float[i] | i | bc_floatref |
aldc_w | cp_String[i] | i | bc_stringref |
cldc_w | 現在のクラス | 0 | bc_classref |
cldc_w | cp_Class[i] | i+1 | bc_classref |
lldc2_w | cp_Long[i] | i | bc_long |
dldc2_w | cp_Double[i] | i | bc_double |
*load | locals[i] | i | bc_local |
*store | locals[i] | i | bc_local |
ret | locals[i] | i | bc_local |
iinc | locals[i] | i | bc_local |
iinc (ワイドではない) | (byte) x | x & 0xFF | bc_byte |
iinc (ワイド) | (short) x | x & 0xFFFF | bc_short |
if** | pc | (増分 (デルタ)/リナンバリング済み) | bc_label |
if_** | pc | (増分 (デルタ)/リナンバリング済み) | bc_label |
goto | pc | (増分 (デルタ)/リナンバリング済み) | bc_label |
jsr | pc | (増分 (デルタ)/リナンバリング済み) | bc_label |
goto_w | pc | (増分 (デルタ)/リナンバリング済み) | bc_label |
jsr_w | pc | (増分 (デルタ)/リナンバリング済み) | bc_label |
tableswitch | ケースカウント | count | bc_case_count |
tableswitch | デフォルト pc | (増分 (デルタ)/リナンバリング済み) | bc_label |
tableswitch | 最初のケース値 | value | bc_case_value |
tableswitch | 各ケース pc | (増分 (デルタ)/リナンバリング済み) | bc_label |
lookupswitch | ケースカウント | count | bc_case_count |
lookupswitch | デフォルト pc | (増分 (デルタ)/リナンバリング済み) | bc_label |
lookupswitch | 各ケース値 | value | bc_case_value |
lookupswitch | 各ケース pc | (増分 (デルタ)/リナンバリング済み) | bc_label |
new | 現在のクラス | 0 | bc_classref |
new | cp_Class[i] | 1+i | bc_classref |
newarray | 型コード | value | bc_byte |
anewarray | 現在のクラス | 0 | bc_classref |
anewarray | cp_Class[i] | 1+i | bc_classref |
checkcast | 現在のクラス | 0 | bc_classref |
checkcast | cp_Class[i] | 1+i | bc_classref |
instanceof | 現在のクラス | 0 | bc_classref |
instanceof | cp_Class[i] | 1+i | bc_classref |
multianewarray | cp_Class[i] | 1+i | bc_classref |
multianewarray | rank | rank & 0xFF | bc_byte |
getstatic | cp_Field[i] | i | bc_fieldref |
putstatic | cp_Field[i] | i | bc_fieldref |
getfield | cp_Field[i] | i | bc_fieldref |
putfield | cp_Field[i] | i | bc_fieldref |
invokevirtual | cp_Method[i] | i | bc_methodref |
invokespecial | cp_Method[i] | i | bc_methodref |
invokestatic | cp_Method[i] | i | bc_methodref |
invokeinterface | cp_Imethod[i] | i | bc_imethodref |
**_this | this_fields[i] | i | bc_thisfield |
**_this | this_methods[i] | i | bc_thismethod |
**_super | super_fields[i] | i | bc_superfield |
**_super | super_methods[i] | i | bc_supermethod |
invokespecial_this_init | this_constructors[i] | i | bc_initref |
invokespecial_super_init | super_constructors[i] | i | bc_initref |
invokespecial_new_init | new_constructors[i] | i | bc_initref |
各整数は、以前に決定されたエンコードに従い、1 - 5 バイトを使用してバイトシーケンスとして転送用にコード化されます。エンコードは、この Pack200 アーカイブ形式仕様の一部として指定された、現在のバンド用のプライマリエンコードの場合もあれば、エンコードされた整数の発行前に転送された情報に基づく場合もあります。
(注:このエンコードアカウントでは、バイトは符号なしの値 [0,255] を表す分割不可能なオクテットです。
名前 | 範囲 | 意味 |
---|---|---|
B | [1..5] | 最大のバイト長 |
H | [1..256] | 上位バイト値の数 |
L | [0..255] | 下位バイト値の数。(256-H) として定義される |
2 つの値 B および H を任意に指定する場合、符号化 (B,H) が存在します。これにより、負以外の整数の初期シーケンスとバイトのショートシーケンスの「エンコードセット」との間で、1 対 1 の対応が確立されます。
さらに、あるエンコードセット内のバイトシーケンスは、同じセット内の別のバイトシーケンスの適切な接頭辞にはなり得ません。これは、そのエンコードが自己サイズ決定、つまり「構文解析可能」であることを非公式に意味します。
また、十分に長いバイトの各シーケンスがエンコードセットから取得されるバイトの一意の接頭辞で始まるように、エンコードセットに可能なかぎり多くが含められます。特に、B バイトの各シーケンスは、(B,H) コーディング用のエンコードセット内に (一意の) 接頭辞を保持します。
特定の (B,H) コーディングでは、バイトのシーケンスはエンコードセット内にあるのは、次のすべてが真の場合だけです。
この定義の結果として、エンコードセットは、上位バイトと下位バイトに関して、この正規表現を満たすと見なされます。
encoding_set = (high* low) | (high)^B
(注:この仕様では、キャレット記号「^」は、前述の正規表現または数値の指数を表します。
n が B 未満の場合、長さが正確に n である (B,H) エンコードの数値は (H^(n-1) * L) になります。長さが正確に B である (B,H) エンコードの数値は、(H^(B-1) * (L+H)) になります。[1..B] 内のすべての n に対するこれらの値の合計は、(B,H) コーディングのエンコードセットの合計サイズになります。これは Card(B,H) と呼ばれ、次のように定義されます。
Card(B,H) = (L * (1-H^B)/(1-H)) + H^B if H>1, or else Card(B,1) = B*255+1
H が 256 の場合、下位バイトは存在しません。エンコードセットは正確に B バイトの実現可能なシーケンスすべてで構成され、Card(B,H) は 256^B になります。
それ以外の場合、エンコードセットは、1 以上 B 以下のさまざまな長さのシーケンスで構成されます。特に、L 個の 1 バイトシーケンスが存在します。これらをほかの箇所で使用して、最高頻度の「優先する」整数をエンコードします。このドキュメントのほかの箇所で指定するエンコードルールは、大きい整数よりも小さい整数をより頻繁に生成するように設計されています。
L は、エンコードする期待値のゼロに関する分布のシャープネスを表現する、「シャープネス」パラメータと見なすことができます。L が大きくなると、エンコードにより、ゼロに近い値の分布が優先されるようになり、ゼロから遠い値の分布はほとんどなくなります。H 値は「平板的な」分布を優先します。これにより、H=256、L=0 までのコーディング範囲内で、完全にランダムなデータのエンコードが優先されます。
N バイト値のシーケンスが b[0] .. b[N-1] である場合、前述のソートベース定義により、このバイトシーケンスの復号化された整数値はリトルエンディアンの H に基づきスケーリングされたバイトの合計になります。また、これは Decode(B,H; b) と呼ばれます。
Decode(B,H; b[*]) = Sum[0<=i<N]( b[i] * H^i )
この定義の結果、エンコードセット内の各バイトシーケンスは、その範囲の算術整数に一意に対応します。この対応は、バイトシーケンスを最初に長さで、次に反辞書式 (リトルエンディアン) 順序でソートすることで実現できます。生成されるシーケンス内の各要素は復号化され、シーケンス内部でゼロベースの独自インデックスになります。
数値ゼロは、常に B 個のゼロバイトのシーケンス (H が 256 の場合)、または単一のゼロバイトによりエンコードされます。
エンコードされる最大の算術値は、常に値 255 の B バイトのシーケンスによりエンコードされます。
(B,H) エンコード (1,256)、(2,256)、および (4,256) は、それぞれ従来の符号なしバイトのリトルエンディアン表現、符号なしの 16 ビット整数、および符号なしの 32 ビット整数と同じです。
中には、2^31-1 (場合によっては 2^32-1) よりも大きい算術値を表すものもあります。これは、(B,H) コーディングスキームの特徴です。ただし、次に示すように、複数の符号ビットが使用される場合に、中間の 64 ビット値が必要になることがあります。次に示す符号付き値の規則では、より複雑なシーケンスにより同じ 32 ビット値 (切り詰め後) が生成される場合でも、圧縮プログラムは必須の 32 ビット整数を転送するのに十分なもっとも単純なエンコードを発行する必要があります。
(B,H) コーディング値を計算する際、実装は中間値を常に自由に切り詰められるわけではありません。ただし、次のセクションで説明するように、最終的な復号化値 (符号の回復後) は、常に 32 ビットに合わせて実行されます。
名前 | 範囲 | 意味 |
---|---|---|
B | [1..5] | 最大のバイト長 |
H | [1..256] | 上位バイト値の数 |
L | [0..255] | 下位バイト値の数。(256-H) として定義される |
S | [0..2] | 符号ビットの数 |
(B,H) コーディングから取得された自然数 U は、パラメータ S に基づく方法で、符号付き 32 ビット値 X に変換されます。この処理は、符号変換と呼ばれます。
簡単にまとめると、S=0 の場合、符号変換は 32 ビットの切り詰めにより実行されます。それ以外の場合、U の下位順ビットは、すべての符号ビットが設定されている場合にのみ X が負になるように、符号ビットとして集合的に処理されます。次に定義するように、U から正の X へ、および U から負の X への 2 つの変換は、それぞれ独自かつ反対方向に稠密および単調です。
(B,H) コーディングとは異なり、(B,H,S) コーディングは特殊な定義範囲内の数値だけを表します。この数値が [-2^31..2^31-1] を超えることは決してありません。
次の部分では、(B,H,S) コーディングごとに Range(B,H,S)、およびこのコーディングを持つ 32 ビット符号値の表現に使用するメソッドを定義します。
(B,H) エンコードバイトシーケンスは、(B,H,S) コーディングの範囲に変換されない自然数 U を表す場合、対応する (B,H,S) コーディングに対する有効な入力ではありません。また、2 つのバイトシーケンスエンコード値 U1 および U2 が存在し、両方が符号付き範囲内の同一ポイント X にマッピングされる場合、U1 と U2 の小さい方のバイトシーケンスだけが有効になります。
このため、(B,H,S) コーディングのカーディナリティーは、無効なエンコードバイトシーケンスのために、対応する (B,H) コーディングのカーディナリティーよりも小さくなります。
Card(B,H) が 2^32 以上である場合、(B,H,S) コーディングの範囲には、32 ビットの符号付き整数 [-2^31..2^31-1] がすべて含まれます。負の値は 31 ビットの符号なしオーバーフローで表されます。コーディングによりエンコードされる符号なしの値が少ない場合、その範囲の最上部が 2^31-1 に、最下部が -2^31 にそれぞれ切り詰められます。
より正確には、(B,H,S) コーディングの範囲は Range(B,H,S) と呼ばれ、次のように定義されます (部分的、S=0 の場合)。
Range(B,H,S) = [-2^31..2^31-1] if S=0, Card(B,H) >= 2^32, or Range(B,H,S) = [0..2^31-1] if S=0, 2^31 < Card(B,H) < 2^32, or else Range(B,H,S) = [0..Card(B,H)-1] if S=0, Card(B,H) <= 2^31圧縮プログラムは、現在のコーディングに従って解釈すると、コーディング範囲外の値に復号化されるバイトシーケンスを発行することはできません。
圧縮プログラムは範囲外のコーディングを発行できないため、圧縮解除プログラムはそれらに対して任意の答えを生成できます。このため、圧縮解除プログラムは、望ましくないラップアラウンドが実行される可能性を無視し、大半の計算で 32 ビット算術を使用できます。
S>0 の場合、最初にバイトシーケンスを (B,H) コーディングとして復号化し、次に復号化した自然数を符号付きの 32 ビット数に変換することで、バイトシーケンスの復号化が実行されます。この符号変換は、(B,H) コーディングから取得する算術値 U、および S の値だけに依存しています。符号変換によりコーディング範囲内の値が生成される場合、算術値は 32 ビット精度より上で保存する必要があります。(B,H) コーディングのカーディナリティーが 2^32 以上の場合、中間の符号なし自然数の 32 ビット切り詰めにより、可能性のある負の符号付き 32 ビット値が (B,H,S) コーディングによりすべて生成されます。中間の符号なし算術を、符号のない 32 ビット数を使って実行することも (いくらかの注意が必要)、64 ビットの符号付き数または符号なし数に対して実行することもできます。
より正確には、符号変換操作 SignConvert(S;U) は、次のように定義されます。
SignConvert(S; U) = Cast32(U) if S==0; or SignConvert(S; U) = U - Floor(U / 2^S) if S>0, (U % 2^S) < 2^S-1, Card(B,H) < 2^32 SignConvert(S; U) = Cast32(U - Floor(U / 2^S)) if S>0, (U % 2^S) < 2^S-1, Card(B,H) >= 2^32 SignConvert(S; U) = -Floor(U / 2^S)-1 if S>0, (U % 2^S) == 2^S-132 ビットで負の大きな符号なし数をラップアラウンドを使って変換する処理は、符号付きの 32 ビット整数にダウンキャストするよく知られた処理と同じです。これは、数学的には次のように定義できます。
Cast32(U) = ((U + 2^31) mod 2^32) - 2^31
SignConvert(S;U) の定義の結果として、U の S 個の下位順ビットは符号ビットとして機能します。実際、U は符号フィールド U0 (S 個の下位順ビットで構成される) および有効数字 U1 (U のその他すべてのビットで構成される) 内に区分けされます。
SignConvert の定義のこの代替ビューでは、U0 のすべての S ビットが設定される場合、復号化された符号付き値が U1 の補数になります。これには U のゼロ上位順ビットが無限の数含まれるため、生成される値は算術的に負になります。
それ以外の場合、U0 の S ビットの一部がクリアであれば、有効数字 U1 は U0 の指定可能な値の数 (S が 2 の場合は (2^2)-1) を使って拡大縮小され、U0 がふたたび加えられます。
これにより、算術間隔 [0..Card(B,H)-1] および Range(B,H,S) の部分間の 1 対 1 の対応が提供されます。
このエンコードで「優先される」整数は、絶対値の小さい値です。ただし、どちらの符号でもかまいません。S が 1 の場合、エンコードでは、正の優先値および負の優先値の数のバランスが取られます。S が 2 (以上) の場合、エンコードは正の数に傾きますが、いくつかの負の数も優先されます。この種のコーディングをほかの場所で使用して、ブランチの置換や大半がソートされたデータの差分など、いくつかが負だがほとんどが正という値をエンコードします。
S=1 の場合、この符号変換定義は、符号なしの右シフトと、それに続く、残りのビットすべてを持つ符号ビットの排他的論理和と等しくなります。
SignConvert(1; U) = (U >>> 1) ^ -(U & 1)
S>0 の場合、Range(B,H,S) は、32 ビット符号範囲 [-2^31..2^31-1] とセット SignConvert(S; [0..Card(B,H)-1]) の交差として単純に定義されます。これは、(B,H) のすべての算術表現値への符号変換を実行する要素単位のアプリケーションにより取得されます。このため、次の方法で範囲の定義を完成できます。
Range(B,H,S) = [-2^31..2^31-1] if Card(B,H) >= 2^32, or Range(B,H,S) = [0..2^31-1] if S=0, 2^31 < Card(B,H) < 2^32, or else Range(B,H,S) = [0..Card(B,H)-1] if S=0, Card(B,H) <= 2^31 Range(B,H,S) = [max( -2^31, min SignConvert(S; Range(B,H,0)) ) .. min( 2^31-1, max SignConvert(S; Range(B,H,0)) )]
(注:符号付きエンコード (B,H,S) の境界を計算する際、(B,H,0) の最大の符号なし値 M から開始し、最初の正および最初の負の値の出現に注意しながら、M、M-1、M-2 という具合に符号変換を実行することは良い方法です。これが、符号付きエンコード範囲の境界になります (境界を範囲に含む)。
Card(B,H) = (L * (1-H^B)/(1-H)) + H^B
(B,H,S) コーディングのカーディナリティーは、その範囲のカーディナリティーにより決定されます。
Card(B,H,S) = Card Range(B,H,S) <= Card(B,H)
定義によると、任意のコーディング (B,H,S) の範囲はゼロに関して稠密であるため、閉鎖区間として表現されます。
Range(B,H,S) = [Min(B,H,S)..Max(B,H,S)] -2^31 <= Min(B,H,S) <= 0 0 < Max(B,H,S) <= 2^31-1
特定の追加属性を、コーディングに持たせることができます。コーディングは、符号付きにも符号なしにもできます。フルレンジにも、サブレンジにも (そのどちらでもないコーディングにすることも) できます。
1 つ以上の負の値でエンコードできる場合、(B,H,S) コーディングは符号付きです。これは、S がゼロ以外であるか、S がゼロで Card(B,H) が 2^32 以上の場合にのみ真になります。
(B,H,S) コーディングがフルレンジになるのは、Range(B,H,S) が [-2^31..2^31-1] の場合です。
サブレンジのコーディングでは、2^31 未満の値が提供されます。より正確に言うと、Card(B,H,S) <= 2^31-1 の場合に、コーディング (B,H,S) はサブレンジになります。コーディングの中には、フルレンジでもサブレンジでもないものもあります。
これらの定義の結果として、B<=3 であるコーディングはすべてサブレンジのコーディングになります。これより長いコーディングも、十分に「シャープ」な場合にはサブレンジにできます。たとえば、(4,192,0)、(5,32,0)、(5,32,1) などが、これに該当します。
また、符号なしのコーディング (4,256,0) は、符号付きのバージョン (4,256,1) と同様にフルレンジです。ただし、二重符号付きのバージョン (4,256,2) はフルレンジではありません。
コーディング (4,255,0) は、カーディナリティーが 2^31 であるため、サブレンジでもフルレンジのコーディングでもありません。これが表現するのは、負でない 32 ビットの整数だけです。カーディナリティーをそのように表すことはできません。
名前 | 範囲 | 意味 |
---|---|---|
B | [1..5] | 最大のバイト長 |
H | [1..256] | 上位バイト値の数 |
L | [0..255] | 下位バイト値の数。(256-H) として定義される |
S | [0..2] | 符号ビットの数 |
D | [0..1] | 増分 (デルタ) エンコードの順序 |
D がゼロの場合、差分化は実行されず、(B,H,S,0) コーディングは常に対応する (B,H,S) コーディングと同一になります。D が 1 の場合、値は後続の差分との関係でエンコードされます。
(注:ほとんど単調に増加してゆくシーケンスは、たいていの場合、(5,H,0,1) 形式の符号なし増分 (デルタ) コーディングを使用して順調にエンコードされます。
値 X[i] のシーケンスが、i の範囲を [0..N-1] として指定された場合を考えてみましょう。このシーケンスは、(B,H,S) コーディングがサブレンジまたはフルレンジのコーディングである場合にのみ、(B,H,S,1) コーディングで表現できます。どちらでもない場合、有効な (B,H,S,1) コーディングは存在しません。
部分和 Sum[j<=i](D[j]) で値 X[i] を表現可能な一連の「増分 (デルタ) 値」D[i] が存在する場合、シーケンス X[i] は表現可能です。
各 (B,H,S,1) コーディングの範囲内に、すべての X[i] 値が含まれます。この範囲は、Range(B,H,S,1) = Range(B,H,0) として定義されます。増分 (デルタ) コーディング (B,H,S,1) の範囲に負の数値が含まれるのは、フルレンジのコーディング (B,H,S) に基づいている場合だけです。それ以外の場合、増分 (デルタ) コーディングは負でない数値だけを表現できます。実際のところ、負のバンド要素はほとんど存在しないため、これは厳しい制限でありません。
部分和 Sum[j<=i](D[j]) の場合、範囲をそのままにしておくことが許可されます。ただし、最終的な X[i] 値は、Card Range(B,H,0) の倍数を加算または減算して常に範囲内に戻されます。
正確には、次のようになります。
X[i] = Sum[j<=i](D[j]) mod Card(B,H,0) if (B,H,S) is sub-range X[i] = (int32) Sum[j<=i](D[j]) if (B,H,S) is full-rangeここで、int32 へのキャストは切り詰めにより 32 ビットにすることを示します。
コーディングがフルレンジの場合、部分和の計算時に、実装は 32 ビットのラップアラウンドを単に無視できます。それ以外の場合、範囲のカーディナリティーの倍数を減算または加算して、部分和を範囲内に注意深く戻す必要があります。また、サイズの範囲が 2^30 以上の場合、32 ビットのラップアラウンドが危険なことがあります。32 ビットの符号付き算術を使用する場合にのみ、これを実装できます。
このような場合、圧縮プログラムは、生成ベースのコーディング変換を使用することがあります。この変換では、N 算術値のシーケンス S が 3 つの値シーケンスに変換されます。
次に、これらの各シーケンス (F、T、U) はサブバンドとして処理され、独自のエンコードを使って転送されます。T 内のゼロでない各値は、F から取得した値をインデックス化します。一方、T 内の各ゼロ値は U から値を順序どおりに選択します。
S[i] = F[T[i]-1] if T[i] != 0 U = { S[i] such that T[i] == 0 }
F の「中央値」X は、F の要素として定義されます。これは、算術的にゼロにもっとも近い値です。F に最小絶対値である正の X および負の -X の両方が含まれる場合、-X が中央値として定義されます。つまり、負の符号には均衡を破る働きがあります。中央値の選択は、優先するエンコードを決めるために行われます。
実装は、32 ビットの符号付き int 式 (X>>31)^(X<<1)
を符号なしの比較鍵として使用して値を比較し、中央を決定できます。
有効なセンチネル値は、F の中央値または F の最終値です。このため、F の解析時に、圧縮解除プログラムは (それまでの) 中央値の繰り返し、または直前の値の繰り返しを検索します。
K を F 内の値の数とし、センチネル値の繰り返しを無視する場合を考えましょう。この場合、次のバンドでは、[1..K] 内の値が 1 から始まるインデックスとして機能し、F の対応する要素を参照します。
T の各要素は、算術範囲 [0..K] でエンコードされた、優先値または非優先値のトークンです。ゼロ以外の各値は、F の要素に順序どおりに対応し、その優先値を生成します。T 内の各ゼロ値は、不明のままの「非優先」値のプレースホルダーです。
すでに説明したように、F の各要素は T の 1 つ以上の要素により参照される必要があります。つまり、次の 2 つのセットは同じになります。
{ F[T[i]-1] such that T[i] != 0 } { F[j] }
コーダーにより F の内容について適切な選択が行われたと仮定すれば、シーケンス T の統計は適切な (B,H) スキームでコンパクトなエンコードを行う上で非常に有利です。一般的に言って、もっともよく利用される入力値を F 内の前方に配置するようにしてください。
最長一致を行うアルゴリズムでは、入力値を発行回数でソートし、もっともよく利用される値を F の先頭に配置することが可能です。これにより、生成される入力のエンコードが改善される可能性があります。この種の手法は、この仕様で要求されているわけではありません。また、非推奨ともされていません。
まとめると、T の要素により選択される F および U の要素は、生成変換の入力と、値および順序が同じになるはずです。
次に、圧縮解除プログラムは、F をメモリーに読み込んでから、T をメモリーに読み込み、そのあと T を再度通過してトークン値を変換し、必要に応じて F を参照するか、U から読み込みます。
適応型のコーディング手法では、値シーケンスを (効果的にサブバンドに) 区分けし、各部分で独自のコーディングをローカルに使用することで、この状況に対応します。
適応型のコーディング手法は、カウント K、K 値のエンコードに使用するコーディング手法 A、および残りの値を処理する別のコーディング手法 B により指定されます。
バンドのサイズはコンテキストで決まるため、適応型のコーディング手法をエンコードされたバンドのバイトに適用する際、このメソッドが復号化する値のカウント N に事前に提供されます。このため、メソッド A が K 値を復号化したあとで、メソッド B を使ってバンド内の残りの (N-K) 値が復号化されます。
圧縮プログラムが、バンドコーディング指示子を提供しないことも可能です。この場合は、バンドのプライマリエンコードにより要素の転送が管理されます。プライマリエンコードで、最初のバンド要素のエンコードがたまたまバンドコーディング指示子であるように見える場合、圧縮プログラムは明示的なバンドコーディング指示子を転送して、バンドのプライマリエンコードを再確認する必要があります。これが必要になるのは、まれなことです。理由は、次で説明するように、バンドエンコード指示子はプライマリエンエンコードで負の数として自分自身を提示するが、大半のバンドでは負の数値はまれとなるからです。
BandCodingSpecifier: (Default | BHSDCode | RunCode | PopCode) BHSDCode: CanonicalBHSDCode | ArbitraryBHSDCode CanonicalBHSDCode: ( '(1,256,0)' | '(1,256,1)' | ... ) ArbitraryBHSDCode: 'arb' ( B H S D ) B, H, S, D: Integer RunCode: 'run' K ACode BCode K: Integer ACode: (Default | BHSDCode | PopCode) BCode: (Default | BHSDCode | RunCode | PopCode) PopCode: 'pop' ( FCode TCode UCode ) FCode: (Default | BHSDCode | RunCode) TCode: (Default | BHSDCode | RunCode) UCode: (Default | BHSDCode | RunCode) Integer: ( '0' | '1' | ... ) Default: 'default'適応型のコーディング手法 (「RunCode」非終端) は、「BCode」非終端を介して連鎖可能ですが、「ACode」非終端を直接介して入れ子にすることはできません。文法の示すとおり、「PopCode」非終端を介して間接的に入れ子にすることは可能です。
また、生成型コーディング手法に適応型コーディング手法を含めることも、適応型コーディング手法に生成型コーディング手法を含めることも可能です。ただし、文法では明示されない点ですが、生成型コーディング手法は間接的であっても入れ子にしてはいけません。
K および L の利用可能な整数値すべてが、等しく十分に表現可能なわけではありません。K に対応する値は圧縮プログラムウィンドウと釣り合う値になり、L に対応する値は BHS エンコードのシャープネスパラメータになります。
S を N および D との関連で適用すると、「S(N,D)」になります。この適用により、転送されたバンドデータの N 要素まで復号化が行われます。
バンドを受信する直前に、圧縮解除プログラムにより、予測されるバンド長 N およびデフォルトのコーディング手法 D が識別され、バンドのプライマリエンコードとして静的に指定されます。次に、圧縮解除プログラムは、バンドエンコード指示子を構成するゼロ以上のバイトを読み取り、バンドエンコード指示子に基づいてバンドデータの N 要素を復号化します。
コーディング指示子が「default」の場合、バンドのプライマリエンコードであるデフォルトコーディング D を使って N 値が復号化されます。バンドの最初の要素が空でないコーディング指示子を導入するように見える場合、この規則が必要になります。このデフォルトコーディング指示子は、圧縮プログラムが発行する義務を負う唯一の種類です。残りの種類の発行はすべてオプションです。
コーディング指示子が別の BHSDCode である場合は、そのコーディングを使って N 値が復号化されます。環境のデフォルト D は無視されます。
コーディング指示子が K、ACode、BCode に対して「run」である場合、2 つの手順が実行されます。最初は、ACode(K,D) のように、指示子 ACode に制御が与えられます。これにより、N が一時的に K に設定されます。次に、BCode(N-K,D) のように、指示子 BCode に制御が与えられます。(注:N が無限の場合、N-K も無限になります。どちらの手順においても、D は変更されません。これにより、ACode や BCode に対する「default」の発行で、D が使用されます。
「run」コーディング指示子の指定する K をゼロにしたり、K 以下の値の実行を「run」コーディング指示子を使って復号化したりすることは、不正な操作です。
コーディング指示子が FCode、TCode、UCode に対して「pop」である場合、3 つの手順が実行されます。最初に、FCode が FCode(infinity, D) として適用されます。F 値の復号化は、重複値にはじめて遭遇した時点で停止します。生成型コーディングに関する前のセクションで説明したように、重複値はセンチネルとして機能します。また、読み取られるもっとも「中央の」値、またはセンチネル値の直前の値でなければいけません。ここでは、K が復号化された一意の値の数であるとします。
F 値を FCode で復号化する際、FCode 内のすべての「run」指示子は、センチネル値を復号化することなくカウント「K」を使い果たす必要があります。つまり、センチネルは、FCode 内の最後の単純な BHSD コーディングで復号化する必要があります。
2 番目の手順では、異なるコーディング TCode が使用されます。これは、T 値は稠密なエンコードであることが期待されるためです。TCode は TCode(N,D) として適用されます。また、トークンが読み取られ、配信されるバンド値が決定されます。指定された N 値が有限の場合は、「pop」コーディング手法の適用のみが可能です。これは、bc_codes など、無限の N を使って読み取られるバンドだけがバイトバンドであるためです。ここでは、Z を、TCode を使って復号化されるゼロの数とします。
3 番目の手順では、UCode を使用し、元のデフォルトエンコード D を利用して Z 値を復号化します。UCode は U(Z,D) として適用されます。すでに説明したように、T シーケンス内のゼロを後続の U 値で置き換え、T シーケンスのほかの要素を F シーケンス内でインデックス化されたものと置き換えることで、バンドが T シーケンスから生成されます。
「pop」コーディング指示子を使って、値のない、または無限数の値の実行を復号化することは不正な操作です。Z (非優先値の数) がゼロの場合、UCode を「default」以外にすることは不正な操作です。
一般的なバンドコーディング指示子「default」は、多くの場合、追加のバイトを必要としない方法でエンコードされます。バンドに要素が存在しない場合、解析は行われません。それ以外の場合、バンドのデフォルトエンコードが可変長であれば、圧縮解除プログラムがある値 X を、バンドのデフォルトコーディング D を使ってバンドの初期バイトから復号化する必要があります。可能な場合、値 X は符号なしのバイト値 XB に変換されます。これがバンドコーディング指示子の最初のバイトになり、X は破棄されます。それ以外の場合、バンドコーディング指示子が「default」になり、D が初期値 X を生成するバイトを含むバンド全体に適用されます。
D は、(B,H,S) または (B,H,S,D) の形式でなければなりません。ただし、B>1、H<256 です。値 D は、最初の値の復号化とは関係がありません。X の復号化は、(B,H,S,0) を使って行われます。S がゼロでなく、値 X が [-256..-1] の範囲内である場合、コーディング指示子のバイト XB が XB = (-1-X) として定義され、X が破棄されます。それ以外の場合、S がゼロで、X の値が [(256-H)..(511-H)] の範囲内であれば、コーディング指示子のバイト XB が XB = (X-(256-H)) として定義され、X が破棄されます。それ以外の場合、コーディング指示子のバイト XB が存在しなければ、すでに説明したように X は最初のバンド値になります。
値 XB が生成された場合は、この値が、実際のバンドデータに先行するコーディング指示子の初期バイトになります。この指示子に対して解析が実行されます。指示子は、XB を先頭とし、band_headers から取得されたバイトへと (必要に応じて) 続きます。そのあと、解析された指示子を使って、次のバイトから始まる、すべてのバンド要素の復号化が制御されます。
バンドが、バンド指示子のバイト XB の生成を必要とする、範囲 ([-256..-1] または [L,L+255]) 内の要素 X1 で実際に始まるが、圧縮プログラムがデフォルトのエンコードの使用を求める場合、X1 が圧縮解除プログラムを誤導することがないように、圧縮プログラムがバンドヘッダーを指定する必要があります。圧縮プログラムがデフォルトコーディングの保持を望む場合、ゼロの XB 値を使って、明示的なコーディング指示子「default」を指定する必要があります (詳細は後述)。このゼロ XB が、デフォルトのエンコードに基づき、X の値 -1 (通常はバイト 1) または L (通常はバイト 192,0) として転送されます。
この設計の直接の結果は、バイトバンドが非デフォルトのエンコードを保持不可能になるが、その他のバンドはすべて保持可能であるという点です。理由は、ほかのバンドはすべて、可変長かつ動的範囲の大きいデフォルトエンコードを保持するためです。X 用に「エスケープ」値が選択されるのは、実際のところまれであるため、通常は実際のバンドデータと混同することはありません。余分にゼロ値を追加してデフォルトを再確認すると、実際のバンドデータの前に常に 1 バイト余分に必要になります。band_headers バンド内に追加のバイトは不要です。一般に、明示的な X 値のエンコードでは、S がゼロの場合は常に 2 バイトが必要になります。S がゼロでない場合は、最大で 2 バイトが必要になります。
バンド band_headers は、バンドヘッダーを持つ各バンドの非初期バンドヘッダーバイト (存在する場合) を転送します。転送順序は、現在の仕様のバンド文法で説明したように、アーカイブ内の全バンドの総合的な順序と一致します。band_headers の長さは、アーカイブヘッダー内で (#band_headers_size として) 独自に指定されます。圧縮解除プログラムは、バンドをさらに読み取る前に band_headers の終端を検出する必要があります。
このため、コーディング指示子のエンコードは、バイトのシーケンス (初期バイト XB、およびゼロまたは band_headers から取得された非初期バイト) で構成されます。前のセクションで指定した小言語のエンコードは、次のようになります。エンコード「default」は、ゼロバイトです。使用可能なすべての BHSDCode エンコードの中で、115 正規エンコードのシーケンス (定義は後述) が選択され、予測される使用範囲が対象になります。BHSDCode は、1 バイトで表現されます。この値は、値が選択した正規エンコードのインデックス (1 ベース) です。
Enc{default} = (0) Enc{CanonicalBHSDCode} = value in [1..115]
特殊な値 116 では、任意の (非正規 BHSD コードの可能性がある) BHSD が導入されます。これについては、続くバイトに記述されます。
Enc{ arb ( B H S D ) } = 116 & (D:[0..1] + 2*S[0..2] + 8*(B:[1..5]-1)) & (H[1..256]-1):B 値は、[1..5] の範囲内でなければなりません。S 値は、[0..2] の範囲内でなければなりません。H 値は、[1..256] の範囲内でなければなりません。D 値は、[0..1] の範囲内でなければなりません。これらの範囲には境界も含まれます。また、B が 1 である場合、H は 256 でなければならず、H が 256 の場合、B は 5 以外でなければなりません。
「run」構文のエンコードは、[117..140] 内のバイトで始まり、オプションのバイト KB を続けることができます。そのあと、1 または 2 つのエンコード指示子 ACode および BCode を続けることができます。117 からのオフセットにより、次のデータがビット単位で表現されます。
Enc{ run ( K ACode BCode ) } = (117 + (KX:[0..3]) + 4*(KBFlag:[0..1]) + 8*(ABDef:[0..2])) & KB: one of [0..255] if KBFlag=1 & Enc{ ACode } if ADef=0 (ABDef != 1) & Enc{ BCode } if BDef=0 (ABDef != 2)
KBFlag が設定されている場合は、先頭バイトの解析後にバイト KB が予測されます。それ以外の場合、KB には値 3 が暗黙的に指定されます。
「run」構文の K 値は、値の範囲を取ることができます。範囲は次のように決定されます。
K = (KB+1) * 16^KX
ADef ビットが設定されている場合、ACode のコーディングは「default」であると理解されます。それ以外の場合は、ACode の表記が次に解析されます。最後に、BDef ビットが設定されている場合、BCode のコーディングは「default」であると理解されます。それ以外の場合は、BCode の表記が最後に解析されます。ADef と BDef の両方をクリアすることは可能ですが、両方を設定することはできません。
「pop」構文のエンコードは [141..188] 内のバイトで始まりますが、それに 1、2、または 3 つのエンコード指示子 FCode、UCode、および TCode を続けることができます。141 からのオフセットにより、次のデータがビット単位でエンコードされます。
Enc{ pop ( FCode TCode UCode ) } = (141 + (FDef:[0..1]) + 2*UDef:[0..1] + 4*(TDefL:[0..11])) & Enc{ FCode } if FDef=0 & Enc{ TCode } if TDef=0 (TDefL==0) & Enc{ UCode } if UDef=0
TDef がゼロの場合、明示的なエンコード指示子により TCode が決定されます。L パラメータは存在しません。TDef が 1 つの場合、L パラメータが存在します。このパラメータは、次の表に従って TDefL から導かれます。
TDefL | L |
---|---|
1 | 4 |
2 | 8 |
3 | 16 |
4 | 32 |
5 | 64 |
6 | 128 |
7 | 192 |
8 | 224 |
9 | 240 |
10 | 248 |
11 | 252 |
L パラメータが存在する場合は、TCode は K および L 値から導かれます。K < 256 の場合、TCode は BYTE1 (1,255,0) になり、L は無視されます。それ以外の場合、TCode は (B,H,S) コーディングになります。ここで、S=0、H=(256-L) です。B は、[0..K] が Range(B,H,S) に含まれる最小値になります。Range(5,256-L,0) に K が含まれないほど、L に大きい値を指定することは不正な操作です。
FDef ビットが設定されている場合、FCode のコーディングは「default」であると理解されます。それ以外の場合、FCode の表記は、初期バイトのあとですぐに解析されます。TDef ビットが設定されている場合、TCode のコーディングは K および L から導かれるものと理解されます。それ以外の場合は、TCode の表記が次に解析されます。最後に、UDef ビットが設定されている場合、UCode のコーディングは「default」であると理解されます。それ以外の場合は、UCode の表記が最後に解析されます。
index | BHSD コーディング |
---|---|
1 | (1,256,0) |
2 | (1,256,1) |
3 | (1,256,0,1) |
4 | (1,256,1,1) |
5 | (2,256,0) |
6 | (2,256,1) |
7 | (2,256,0,1) |
8 | (2,256,1,1) |
9 | (3,256,0) |
10 | (3,256,1) |
11 | (3,256,0,1) |
12 | (3,256,1,1) |
13 | (4,256,0) |
14 | (4,256,1) |
15 | (4,256,0,1) |
16 | (4,256,1,1) |
17 | (5, 4,0) |
18 | (5, 4,1) |
19 | (5, 4,2) |
20 | (5, 16,0) |
21 | (5, 16,1) |
22 | (5, 16,2) |
23 | (5, 32,0) |
24 | (5, 32,1) |
25 | (5, 32,2) |
26 | (5, 64,0) |
27 | (5, 64,1) |
28 | (5, 64,2) |
29 | (5,128,0) |
30 | (5,128,1) |
31 | (5,128,2) |
32 | (5, 4,0,1) |
33 | (5, 4,1,1) |
34 | (5, 4,2,1) |
35 | (5, 16,0,1) |
36 | (5, 16,1,1) |
37 | (5, 16,2,1) |
38 | (5, 32,0,1) |
39 | (5, 32,1,1) |
40 | (5, 32,2,1) |
41 | (5, 64,0,1) |
42 | (5, 64,1,1) |
43 | (5, 64,2,1) |
44 | (5,128,0,1) |
45 | (5,128,1,1) |
46 | (5,128,2,1) |
47 | (2,192,0) |
48 | (2,224,0) |
49 | (2,240,0) |
50 | (2,248,0) |
51 | (2,252,0) |
52 | (2, 8,0,1) |
53 | (2, 8,1,1) |
54 | (2, 16,0,1) |
55 | (2, 16,1,1) |
56 | (2, 32,0,1) |
57 | (2, 32,1,1) |
58 | (2, 64,0,1) |
59 | (2, 64,1,1) |
60 | (2,128,0,1) |
61 | (2,128,1,1) |
62 | (2,192,0,1) |
63 | (2,192,1,1) |
64 | (2,224,0,1) |
65 | (2,224,1,1) |
66 | (2,240,0,1) |
67 | (2,240,1,1) |
68 | (2,248,0,1) |
69 | (2,248,1,1) |
70 | (3,192,0) |
71 | (3,224,0) |
72 | (3,240,0) |
73 | (3,248,0) |
74 | (3,252,0) |
75 | (3, 8,0,1) |
76 | (3, 8,1,1) |
77 | (3, 16,0,1) |
78 | (3, 16,1,1) |
79 | (3, 32,0,1) |
80 | (3, 32,1,1) |
81 | (3, 64,0,1) |
82 | (3, 64,1,1) |
83 | (3,128,0,1) |
84 | (3,128,1,1) |
85 | (3,192,0,1) |
86 | (3,192,1,1) |
87 | (3,224,0,1) |
88 | (3,224,1,1) |
89 | (3,240,0,1) |
90 | (3,240,1,1) |
91 | (3,248,0,1) |
92 | (3,248,1,1) |
93 | (4,192,0) |
94 | (4,224,0) |
95 | (4,240,0) |
96 | (4,248,0) |
97 | (4,252,0) |
98 | (4, 8,0,1) |
99 | (4, 8,1,1) |
100 | (4, 16,0,1) |
101 | (4, 16,1,1) |
102 | (4, 32,0,1) |
103 | (4, 32,1,1) |
104 | (4, 64,0,1) |
105 | (4, 64,1,1) |
106 | (4,128,0,1) |
107 | (4,128,1,1) |
108 | (4,192,0,1) |
109 | (4,192,1,1) |
110 | (4,224,0,1) |
111 | (4,224,1,1) |
112 | (4,240,0,1) |
113 | (4,240,1,1) |
114 | (4,248,0,1) |
115 | (4,248,1,1) |
ただし、すべての Pack200 アーカイブで、各圧縮解除プログラムが、転送されるクラスファイルごとに特定のバイト単位のイメージを生成する必要があります。これは、メッセージダイジェストなど、転送されるクラスファイルの最終的なバイト単位の内容にかかわる情報を、圧縮プログラムが転送できるようにするために、圧縮解除プログラムに科せられる要件です。このセクションでは、出力ファイルのバイト単位の内容を洗練された入力関数に変換する、各圧縮解除プログラムに科される制限について説明します。
一般に、圧縮解除されたクラスファイル内の要素の順序は、Pack200 アーカイブ内の転送順序と一致している必要があります。たとえば、クラスファイル内で宣言されるクラスのフィールドの順序は、クラスのフィールド記述子が field_descr バンド内で転送された順序に対応している必要があります。これは、field_flags バンド内の順序にも対応します。次の表では、クラスファイルの順序とアーカイブの転送順序に関する、必須の対応関係をすべて示します。
クラスファイルの 要素 |
順序を決定する バンド |
---|---|
実装されるインタフェース | class_interface |
宣言されるフィールド | field_descr |
宣言されるメソッド | method_descr |
コードハンドラのリスト | code_handler_start_P |
クラス属性のリスト | class_flags、class_attr_indexes |
フィールド属性のリスト | field_flags、field_attr_indexes |
メソッド属性のリスト | method_flags、method_attr_indexes |
コード属性のリスト | code_attr_indexes |
定数プールエントリ | cp_Utf8 など (詳細は後述) |
総合すると、順序に関するこれらの必須対応関係により、圧縮解除されたクラスファイルの内容が厳密に決定されます。インタフェース、フィールド、メソッド、および例外ハンドラの順序は、これらを転送するバンドの順序により直接決定されます。
InnerClasses 属性をクラスに追加する必要がある場合は、次のセクションで説明する規則に従って、その属性をクラスの属性リストの最後に配置する必要があります。
そのあと、cp(X) が、次の手順に従うかのように定義されます。これにより、cp(X) がグローバル定数プール cp_All から生成されます。また、X の InnerClasses 属性も生成される可能性があります。
この時点で、定数プール cp(X) は、圧縮解除プログラムが関連するネストされたクラスのセット ic_Relevant(X) を計算できるほど、十分に定義されています。
ic_Relevant(X) およびオプションで転送された ic_Local(X) を制御下に置いた圧縮解除プログラムは、ここで次の手順を実行して、X の InnerClasses 属性 ic_Stored(X) を格納するかどうかを決定する必要があります。
ネストされたクラスのレコードからの最後の処理として、圧縮解除プログラムにより、次の手順で定数プールの仕上げが行われます。
この処理のあとで、X の格納済み定数プールは参照整合性を維持し、すべての定数参照を cp(X) 内にインデックスとしてコード化できます。定数参照の明確な順序は、cp_All から導かれます。特に、cp(X) 内の定数が別の定数を参照する必要がある場合は、閉包手順の際に、その定数が cp(X) 内に挿入済みである必要があります。
cp(X) の順序は cp_All の順序と一致しています。ただし、一部の署名参照が cp_Utf8 の同等の既存要素にリダイレクトされること、および ldc バイトコードのオペランドすべてが定数プールの先頭に強制的に配置されることを除きます。
ローカルインデックスを cp(X) の要素に割り当てる際、圧縮解除プログラムは、クラスファイル形式の要件に従い、インデックスゼロ、CONSTANT_Long および CONSTANT_Double 定数用の空の定数プールスロットの予約を順守する必要があります。これらの規則に加え、cp(X) の構築および順序付けにより、X 内部の定数参照に対する明確なインデックス割り当てが完全に決定されます。
バンド | デフォルトの コーディング |
長さ: | 参照される 定数プール |
---|---|---|---|
archive_magic | BYTE1 | [4] | |
archive_header | UNSIGNED5 | [26] | |
band_headers | BYTE1 | [...] | |
cp_Utf8_prefix | DELTA5 | [MAX(0,#cp_Utf8_count-2)] | |
cp_Utf8_suffix | UNSIGNED5 | [MAX(0,#cp_Utf8_count-1)] | |
cp_Utf8_chars | CHAR3 | [SUM(*cp_Utf8_suffix)] | |
cp_Utf8_big_suffix | DELTA5 | [COUNT(0,*cp_Utf8_suffix)] | |
{cp_Utf8_big_chars...} | DELTA5 | [*cp_Utf8_big_suffix[i]] | |
cp_Int | UDELTA5 | [#cp_Int_count] | |
cp_Float | UDELTA5 | [#cp_Float_count] | |
cp_Long_hi | UDELTA5 | [#cp_Long_count] | |
cp_Long_lo | DELTA5 | [#cp_Long_count] | |
cp_Double_hi | UDELTA5 | [#cp_Double_count] | |
cp_Double_lo | DELTA5 | [#cp_Double_count] | |
cp_String | UDELTA5 | [#cp_String_count] | cp_Utf8 |
cp_Class | UDELTA5 | [#cp_Class_count] | cp_Utf8 |
cp_Signature_form | DELTA5 | [#cp_Signature_count] | cp_Utf8 |
cp_Signature_classes | UDELTA5 | [COUNT('L',...)] | cp_Class |
cp_Descr_name | DELTA5 | [#cp_Descr_count] | cp_Utf8 |
cp_Descr_type | UDELTA5 | [#cp_Descr_count] | cp_Signature |
cp_Field_class | DELTA5 | [#cp_Field_count] | cp_Class |
cp_Field_desc | UDELTA5 | [#cp_Field_count] | cp_Descr |
cp_Method_class | DELTA5 | [#cp_Method_count] | cp_Class |
cp_Method_desc | UDELTA5 | [#cp_Method_count] | cp_Descr |
cp_Imethod_class | DELTA5 | [#cp_Imethod_count] | cp_Class |
cp_Imethod_desc | UDELTA5 | [#cp_Imethod_count] | cp_Descr |
attr_definition_headers | BYTE1 | [#attr_definition_count] | |
attr_definition_name | UNSIGNED5 | [#attr_definition_count] | cp_Utf8 |
attr_definition_layout | UNSIGNED5 | [#attr_definition_count] | cp_Utf8 |
ic_this_class | UDELTA5 | [#ic_count] | cp_Class |
ic_flags | UNSIGNED5 | [#ic_count] | |
ic_outer_class | DELTA5 | [COUNT(1<<16,...)] | cp_Class |
ic_name | DELTA5 | [COUNT(1<<16,...)] | cp_Utf8 |
class_this | DELTA5 | [#class_count] | cp_Class |
class_super | DELTA5 | [#class_count] | cp_Class |
class_interface_count | DELTA5 | [#class_count] | |
class_interface | DELTA5 | [SUM(*class_interface_count)] | cp_Class |
class_field_count | DELTA5 | [#class_count] | |
class_method_count | DELTA5 | [#class_count] | |
field_descr | DELTA5 | [SUM(*class_field_count)] | cp_Descr |
field_flags_hi | UNSIGNED5 | [SUM(*class_field_count)*#have_field_flags_hi] | |
field_flags_lo | UNSIGNED5 | [SUM(*class_field_count)] | |
field_attr_count | UNSIGNED5 | [COUNT(1<<16,...)] | |
field_attr_indexes | UNSIGNED5 | [SUM(*field_attr_count)] | |
field_attr_calls | UNSIGNED5 | [...] | |
field_ConstantValue_KQ | UNSIGNED5 | [COUNT(ConstantValue,...)] | cp_Int、cp_Float など。 |
field_Signature_RS | UNSIGNED5 | [COUNT(Signature,...)] | cp_Signature |
field_RVA_anno_N | UNSIGNED5 | [...] | |
field_RVA_type_RS | UNSIGNED5 | [...] | cp_Signature |
field_RVA_pair_N | UNSIGNED5 | [...] | |
field_RVA_name_RU | UNSIGNED5 | [...] | cp_Utf8 |
field_RVA_T | BYTE1 | [...] | |
field_RVA_caseI_KI | UNSIGNED5 | [...] | cp_Int |
field_RVA_caseD_KD | UNSIGNED5 | [...] | cp_Double |
field_RVA_caseF_KF | UNSIGNED5 | [...] | cp_Float |
field_RVA_caseJ_KJ | UNSIGNED5 | [...] | cp_Long |
field_RVA_casec_RS | UNSIGNED5 | [...] | cp_Signature |
field_RVA_caseet_RS | UNSIGNED5 | [...] | cp_Signature |
field_RVA_caseec_RU | UNSIGNED5 | [...] | cp_Utf8 |
field_RVA_cases_RU | UNSIGNED5 | [...] | cp_Utf8 |
field_RVA_casearray_N | UNSIGNED5 | [...] | |
field_RVA_nesttype_RS | UNSIGNED5 | [...] | cp_Signature |
field_RVA_nestpair_N | UNSIGNED5 | [...] | |
field_RVA_nestname_RU | UNSIGNED5 | [...] | cp_Utf8 |
field_RIA_anno_N | UNSIGNED5 | [...] | |
{field_RIA_...} | |||
field_RIA_nestname_RU | UNSIGNED5 | [...] | cp_Utf8 |
{field_attr_bands...} | (各種) | [...] | (各種) |
method_descr | MDELTA5 | [SUM(*class_method_count)] | cp_Descr |
method_flags_hi | UNSIGNED5 | [SUM(*class_method_count)*#have_method_flags_hi] | |
method_flags_lo | UNSIGNED5 | [SUM(*class_method_count)] | |
method_attr_count | UNSIGNED5 | [COUNT(1<<16,...)] | |
method_attr_indexes | UNSIGNED5 | [SUM(*method_attr_count)] | |
method_attr_calls | UNSIGNED5 | [...] | |
method_Exceptions_N | UNSIGNED5 | [COUNT(Exceptions,...)] | |
method_Exceptions_RC | UNSIGNED5 | [SUM(*method_Exceptions_N)] | cp_Class |
method_Signature_RS | UNSIGNED5 | [COUNT(Signature,...)] | cp_Signature |
method_RVA_anno_N | UNSIGNED5 | [...] | |
method_RVA_type_RS | UNSIGNED5 | [...] | cp_Signature |
method_RVA_pair_N | UNSIGNED5 | [...] | |
method_RVA_name_RU | UNSIGNED5 | [...] | cp_Utf8 |
method_RVA_T | BYTE1 | [...] | |
method_RVA_caseI_KI | UNSIGNED5 | [...] | cp_Int |
method_RVA_caseD_KD | UNSIGNED5 | [...] | cp_Double |
method_RVA_caseF_KF | UNSIGNED5 | [...] | cp_Float |
method_RVA_caseJ_KJ | UNSIGNED5 | [...] | cp_Long |
method_RVA_casec_RS | UNSIGNED5 | [...] | cp_Signature |
method_RVA_caseet_RS | UNSIGNED5 | [...] | cp_Signature |
method_RVA_caseec_RU | UNSIGNED5 | [...] | cp_Utf8 |
method_RVA_cases_RU | UNSIGNED5 | [...] | cp_Utf8 |
method_RVA_casearray_N | UNSIGNED5 | [...] | |
method_RVA_nesttype_RS | UNSIGNED5 | [...] | cp_Signature |
method_RVA_nestpair_N | UNSIGNED5 | [...] | |
method_RVA_nestname_RU | UNSIGNED5 | [...] | cp_Utf8 |
method_RIA_anno_N | UNSIGNED5 | [...] | |
{method_RIA_...} | |||
method_RIA_nestname_RU | UNSIGNED5 | [...] | cp_Utf8 |
method_RVPA_param_NB | BYTE1 | [...] | |
method_RVPA_anno_N | UNSIGNED5 | [...] | |
{method_RVPA_...} | |||
method_RVPA_nestname_RU | UNSIGNED5 | [...] | cp_Utf8 |
method_RIPA_param_NB | BYTE1 | [...] | |
method_RIPA_anno_N | UNSIGNED5 | [...] | |
{method_RIPA_...} | |||
method_RIPA_nestname_RU | UNSIGNED5 | [...] | cp_Utf8 |
method_AD_T | BYTE1 | [...] | |
method_AD_caseI_KI | UNSIGNED5 | [...] | cp_Int |
method_AD_caseD_KD | UNSIGNED5 | [...] | cp_Double |
method_AD_caseF_KF | UNSIGNED5 | [...] | cp_Float |
method_AD_caseJ_KJ | UNSIGNED5 | [...] | cp_Long |
method_AD_casec_RS | UNSIGNED5 | [...] | cp_Signature |
method_AD_caseet_RS | UNSIGNED5 | [...] | cp_Signature |
method_AD_caseec_RU | UNSIGNED5 | [...] | cp_Utf8 |
method_AD_cases_RU | UNSIGNED5 | [...] | cp_Utf8 |
method_AD_casearray_N | UNSIGNED5 | [...] | |
method_AD_nesttype_RS | UNSIGNED5 | [...] | cp_Signature |
method_AD_nestpair_N | UNSIGNED5 | [...] | |
method_AD_nestname_RU | UNSIGNED5 | [...] | cp_Utf8 |
{method_attr_bands...} | (各種) | [...] | (各種) |
class_flags_hi | UNSIGNED5 | [#class_count*#have_class_flags_hi] | |
class_flags_lo | UNSIGNED5 | [#class_count] | |
class_attr_count | UNSIGNED5 | [COUNT(1<<16,...)] | |
class_attr_indexes | UNSIGNED5 | [SUM(*class_attr_count)] | |
class_attr_calls | UNSIGNED5 | [...] | |
class_SourceFile_RUN | UNSIGNED5 | [COUNT(SourceFile,...)] | null|cp_Utf8 |
class_EnclosingMethod_RC | UNSIGNED5 | [COUNT(EnclosingMethod,...)] | cp_Class |
class_EnclosingMethod_RDN | UNSIGNED5 | [COUNT(EnclosingMethod,...)] | null|cp_Descr |
class_Signature_RS | UNSIGNED5 | [COUNT(Signature,...)] | cp_Signature |
class_RVA_anno_N | UNSIGNED5 | [...] | |
class_RVA_type_RS | UNSIGNED5 | [...] | cp_Signature |
class_RVA_pair_N | UNSIGNED5 | [...] | |
class_RVA_name_RU | UNSIGNED5 | [...] | cp_Utf8 |
class_RVA_T | BYTE1 | [...] | |
class_RVA_caseI_KI | UNSIGNED5 | [...] | cp_Int |
class_RVA_caseD_KD | UNSIGNED5 | [...] | cp_Double |
class_RVA_caseF_KF | UNSIGNED5 | [...] | cp_Float |
class_RVA_caseJ_KJ | UNSIGNED5 | [...] | cp_Long |
class_RVA_casec_RS | UNSIGNED5 | [...] | cp_Signature |
class_RVA_caseet_RS | UNSIGNED5 | [...] | cp_Signature |
class_RVA_caseec_RU | UNSIGNED5 | [...] | cp_Utf8 |
class_RVA_cases_RU | UNSIGNED5 | [...] | cp_Utf8 |
class_RVA_casearray_N | UNSIGNED5 | [...] | |
class_RVA_nesttype_RS | UNSIGNED5 | [...] | cp_Signature |
class_RVA_nestpair_N | UNSIGNED5 | [...] | |
class_RVA_nestname_RU | UNSIGNED5 | [...] | cp_Utf8 |
class_RIA_anno_N | UNSIGNED5 | [...] | |
{class_RIA_...} | |||
class_RIA_nestname_RU | UNSIGNED5 | [...] | cp_Utf8 |
class_InnerClasses_N | UNSIGNED5 | [COUNT(InnerClasses,...)] | |
class_InnerClasses_RC | UNSIGNED5 | [SUM(*class_InnerClasses_N)] | cp_Class |
class_InnerClasses_F | UNSIGNED5 | [SUM(*class_InnerClasses_N)] | |
class_InnerClasses_outer_RCN | UNSIGNED5 | [COUNT(!=0,*class_InnerClasses_F)] | null|cp_Class |
class_InnerClasses_name_RUN | UNSIGNED5 | [COUNT(!=0,*class_InnerClasses_F)] | null|cp_Utf8 |
class_file_version_minor_H | UNSIGNED5 | [COUNT(version,...)] | |
class_file_version_major_H | UNSIGNED5 | [COUNT(version,...)] | |
{class_attr_bands...} | (各種) | [...] | (各種) |
code_headers | BYTE1 | [COUNT(Code,...)] | |
code_max_stack | UNSIGNED5 | [COUNT(0,*code_headers)] | |
code_max_na_locals | UNSIGNED5 | [COUNT(0,*code_headers)] | |
code_handler_count | UNSIGNED5 | [COUNT(0,*code_headers)] | |
code_handler_start_P | BCI5 | [SUM(*code_header_count)] | |
code_handler_end_PO | BRANCH5 | [SUM(*code_header_count)] | |
code_handler_catch_PO | BRANCH5 | [SUM(*code_header_count)] | |
code_handler_class_RCN | UNSIGNED5 | [SUM(*code_header_count)] | null|cp_Class |
code_flags_hi | UNSIGNED5 | [...*#have_code_flags_hi] | |
code_flags_lo | UNSIGNED5 | [...] | |
code_attr_count | UNSIGNED5 | [COUNT(1<<16,...)] | |
code_attr_indexes | UNSIGNED5 | [SUM(*code_attr_count)] | |
code_attr_calls | UNSIGNED5 | [...] | |
code_StackMapTable_N | UNSIGNED5 | [COUNT(StackMapTable,...)] | |
code_StackMapTable_frame_T | BYTE1 | [SUM(*code_StackMapTable_N)] | |
code_StackMapTable_local_N | UNSIGNED5 | [COUNT(255,*code_StackMapTable_frame_T)] | |
code_StackMapTable_stack_N | UNSIGNED5 | [COUNT(255,*code_StackMapTable_frame_T)] | |
code_StackMapTable_offset | UNSIGNED5 | [...] | |
code_StackMapTable_T | BYTE1 | [...] | |
code_StackMapTable_RC | UNSIGNED5 | [COUNT(7,*code_StackMapTable_T)] | |
code_StackMapTable_P | BCI5 | [COUNT(8,*code_StackMapTable_T)] | |
code_LineNumberTable_N | UNSIGNED5 | [...] | |
code_LineNumberTable_bci_P | BCI5 | [...] | |
code_LineNumberTable_line | UNSIGNED5 | [...] | |
code_LocalVariableTable_N | UNSIGNED5 | [...] | |
code_LocalVariableTable_bci_P | BCI5 | [...] | |
code_LocalVariableTable_span_O | BRANCH5 | [...] | |
code_LocalVariableTable_name_RU | UNSIGNED5 | [...] | cp_Utf8 |
code_LocalVariableTable_type_RS | UNSIGNED5 | [...] | cp_Signature |
code_LocalVariableTable_slot | UNSIGNED5 | [...] | |
code_LocalVariableTypeTable_N | UNSIGNED5 | [...] | |
code_LocalVariableTypeTable_bci_P | BCI5 | [...] | |
code_LocalVariableTypeTable_span_O | BRANCH5 | [...] | |
code_LocalVariableTypeTable_name_RU | UNSIGNED5 | [...] | cp_Utf8 |
code_LocalVariableTypeTable_type_RS | UNSIGNED5 | [...] | cp_Signature |
code_LocalVariableTypeTable_slot | UNSIGNED5 | [...] | |
{code_attr_bands...} | (各種) | [...] | (各種) |
bc_codes | BYTE1 | [...] | |
bc_case_count | UNSIGNED5 | [COUNT(switch,*bc_codes)] | |
bc_case_value | DELTA5 | [...] | |
bc_byte | BYTE1 | [...] | |
bc_short | DELTA5 | [...] | |
bc_local | UNSIGNED5 | [...] | |
bc_label | BRANCH5 | [...] | |
bc_intref | DELTA5 | [...] | cp_Int |
bc_floatref | DELTA5 | [...] | cp_Float |
bc_longref | DELTA5 | [...] | cp_Long |
bc_doubleref | DELTA5 | [...] | cp_Double |
bc_stringref | DELTA5 | [...] | cp_String |
bc_classref | UNSIGNED5 | [...] | cp_Class |
bc_fieldref | DELTA5 | [...] | cp_Field |
bc_methodref | UNSIGNED5 | [...] | cp_Method |
bc_imethodref | DELTA5 | [...] | cp_Imethod |
bc_thisfield | UNSIGNED5 | [...] | cp_Field サブシーケンス |
bc_superfield | UNSIGNED5 | [...] | cp_Field サブシーケンス |
bc_thismethod | UNSIGNED5 | [...] | cp_Method サブシーケンス |
bc_supermethod | UNSIGNED5 | [...] | cp_Method サブシーケンス |
bc_initref | UNSIGNED5 | [...] | cp_Method サブシーケンス |
bc_escref | UNSIGNED5 | [...] | cp_All |
bc_escrefsize | UNSIGNED5 | [...] | |
bc_escsize | UNSIGNED5 | [...] | |
bc_escbyte | BYTE1 | [...] | |
file_name | UNSIGNED5 | [#file_count] | cp_Utf8 |
file_size_hi | UNSIGNED5 | [#file_count*(#have_file_size_hi)] | |
file_size_lo | UNSIGNED5 | [#file_count] | |
file_modtime | DELTA5 | [#file_count*(#have_file_modtime)] | |
file_options | UNSIGNED5 | [#file_count*(#have_file_options)] | |
file_bits | BYTE1 | [SUM(*file_size)] |
assert(cp_Utf8[0].equals("")); int cursor = 0; int big_cursor = 0; for (int i = 1; i < cp_Utf8_count; i++) { String thisString = cp_Utf8[i]; int prefix = (i == 1)? 0: cp_Utf8_prefix[i-2]; int suffix = thisString.length() - prefix; String prevString = cp_Utf8[i-1]; String prevPrefix = prevString.substring(0, prefix); String thisPrefix = thisString.substring(0, prefix); assert(prevPrefix.equals(thisPrefix)); int small_suffix = cp_Utf8_suffix[i-1]; char[] suffix_chars; int offset; if (small_suffix != 0) { assert(suffix == small_suffix); suffix_chars = cp_Utf8_chars; offset = cursor; cursor += suffix; } else { assert(suffix == cp_Utf8_big_suffix[big_cursor]); suffix_chars = cp_Utf8_big_chars[big_cursor]; offset = 0; assert(suffix == suffix_chars.length); big_cursor += 1; } String thisSuffix = thisString.substring(prefix); String theseChars = new String(suffix_chars, offset, suffix); assert(thisSuffix.equals(theseChars)); } assert(cp_Utf8_prefix.length == Math.max(0, cp_Utf8_count-2)); assert(cp_Utf8_suffix.length == Math.max(0, cp_Utf8_count-1)); assert(cp_Utf8_chars.length == cursor); assert(cp_Utf8_big_suffix.length == big_cursor); assert(cp_Utf8_big_chars.length == big_cursor);
int cursor = 0; for (int i = 0; i < cp_Signature_count; i++) { String sign = cp_Signature[i]; String form = cp_Signature_form[i]; int form_ptr = 0; int sign_ptr = 0; for (; form_ptr < form.length(); form_ptr++) { assert(form.charAt(form_ptr) == sign.charAt(sign_ptr)); sign_ptr += 1; if (form.charAt(form_ptr) == 'L') { String cls = cp_Class[cursor]; assert(sign.startsWith(cls, sign_ptr)); cursor += 1; sign_ptr += cls.length(); } } assert(sign_ptr == sign.length()); } assert(cp_Signature_form.length == cp_Signature_count); assert(cp_Signature_classes.length == cursor);
// ins_pos is a display of all instruction boundaries int[] ins_pos; ... Arrays.sort(ins_pos); assert(ins_pos[0] == 0); assert(ins_pos[ins_pos.length-1] == bytecodes.length); int regulars = ins_pos.length; // # instruction boundaries ... int renumber_bci(int bci) { int i = Arrays.binarySearch(ins_pos, bci); return (i >= 0) ? i : (i == -1) ? bci : ins_pos.length + bci - (-i-1); } ... for (int bci = -100; bci <= bytecodes.length+100; bci++) { int bci_numbering_for_band = renumber_bci(bci); int i = Arrays.binarySearch(ins_pos, bci); if (i >= 0) { assert(ins_pos[i] == bci); assert(bci_numbering_for_band < regulars); assert(bci_numbering_for_band == i); } else if (0 < bci && bci < bytecodes.length) { int nexti = (-i-1); // index of next instruction assert(ins_pos[nexti-1] < bci && bci < ins_pos[nexti]); int prevRegulars = nexti; int prevAll = bci; int prevIrregulars = prevAll - prevRegulars; assert(bci_numbering_for_band >= regulars); assert(bci_numbering_for_band == regulars + prevIrregulars); } else { // other (random) numbers are unchanged by renumbering assert(bci_numbering_for_band >= bytecodes.length || bci_numbering_for_band < 0); assert(bci_numbering_for_band == bci); } }
String bcn, predictableOuter, predictableICName; ... String name = bcn.substring(bcn.lastIndexOf('/')+1); int dollar2 = name.lastIndexOf('$'); assert(predictableICName == null || (predictableICName.charAt(0) > '9' && predictableICName.indexOf('$') < 0)); if (predictableICName == null) { // bcnCase1 or bcnCase4 assert(predictableOuter == null); assert(dollar2 == -1 || dollar2+1 == name.length() || isDigit(name.charAt(dollar2+1))); } else if (predictableOuter == null) { // bcnCase2 assert(name.endsWith("$"+predictableICName)); int dollar1 = name.substring(0, dollar2).lastIndexOf('$'); assert(dollar1 >= 0 && dollar1+1 < dollar2); assert(isDigitString(name.substring(dollar1+1, dollar2))); } else { // bcnCase3 assert(bcn.equals(predictableOuter+"$"+predictableICName)); }
(一部の手法では圧縮パフォーマンス乗数 1.002 以上は、パフォーマンスに容易に反映されてエンドユーザーの注目を集めるため、大きな意味を持ちます。1.0005 未満の乗数は意味をなしません。)
Pack200 は、その他多くの圧縮アルゴリズムと同様に、圧縮するアーカイブの内容選択に関してかなりの程度の自由を圧縮プログラムに与えていますが、圧縮解除される JAR ファイルの内容の選択については、一切の自由を与えていません。Pack200 に準拠したすべての圧縮解除プログラムは、圧縮されたアーカイブが指定されたなら、転送されたクラスファイルごとに同じクラスファイルバイトを生成する必要があります。このクラスファイルに関する出力安定性は、リソースファイル (マニフェストなど) の内容が変更されても持続します。このため、指定したクラスファイルに関しては、圧縮は不可逆であっても、Pack200 の仕様に定義された有効な方法を使って圧縮解除で正確に同じ結果が得られます。
このため、正しい安定性プロパティーを持つ圧縮プログラムを使って、パックされた署名付き JAR を生成できます。次の手順を実行します。
(注:この仕様に準拠した 2 つの圧縮解除プログラムが、同一の圧縮済みアーカイブ入力に対して異なる JAR アーカイブ要素を生成することがあるなら、それは仕様のバグです。仕様には、この種のバグは存在しないと考えられていますが、万一見つけた場合はレポートを送りください。)
Pack200 仕様では、ファイル名文字列の解釈が、ほかのツールやオペレーティングシステムとの関係で規定されることはありません。ただし、自然なマッピングが可能なかぎり Java の使用法に近いものになります。
JAR および ZIP は、日付を、タイムゾーンの指定を含まないローカル形式 ("YYYY/MM/DD HH:MM:SS") で格納します。このため、Java の UTC ベースの時間との相互変換を行うには、圧縮プログラムが動作していたタイムゾーンを推測する必要があります。(これは Pack200 に特有の問題ではありません。ZIP および JAR アーカイブの用途すべてで発生する問題です。) さまざまな場面で使用される標準的な推定方法は、圧縮解除プログラムと圧縮プログラムが同じタイムゾーン、および同じサマータイム制度期間中に動作したと見なす方法です。ただし、Pack200 アーカイブ内での時間転送の安定性を高めるため、ZIP 形式のローカル時間を解釈する際に、大半の圧縮プログラムおよび圧縮解除プログラムがタイムゾーンとして UTC を使用することに同意することが期待されています。
この設計をサポートするための考慮事項は、次のとおりです。
アンパックプログラムの実装では、各標準バージョンのアーカイブフォーマットをサポートすることが強く推奨されています。これは、配備チャネルの各側にあるパックプログラムとアンパックプログラムのバージョン間に、強力な調整機能が常に存在するとはかぎらないためです。パックプログラムの実装では、アンパックプログラムとの互換性を最大限維持するために、以前のアーカイブ形式の発行機能を保持することが推奨されています。
入力 JAR アーカイブに 1.5 (以下の) クラスファイルが含まれる場合、参照実装は 1.5 パック形式を生成して下位互換性を維持します。それ以外の場合は、1.6 パックファイルが生成されます。参照実装のアンパックプログラムは、以前のすべての標準バージョンと互換性があります。