0

HashMap<String,Double>を使用する代わりに、エントリを反復してそれぞれをシリアル化することにより、 a をシリアル化するコードをいくつか書きましObjectOutputStream.readObject()た。その理由は単に効率です。結果として得られるファイルははるかに小さく、書き込みと読み取りがはるかに高速です (たとえば、0.6 秒で 23 MB、9.9 秒で 29 MB)。

これは私がシリアル化するためにしたことです:

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.bin"));
oos.writeInt(map.size()); // write size of the map
for (Map.Entry<String, Double> entry : map.entrySet()) { // iterate entries
    System.out.println("writing ("+ entry.getKey() +","+ entry.getValue() +")");
    byte[] bytes = entry.getKey().getBytes();
    oos.writeInt(bytes.length); // length of key string
    oos.write(bytes); // key string bytes
    oos.writeDouble(entry.getValue()); // value
}
oos.close();

ご覧のとおりbyte、各 key の配列を取得し、Stringその長さをシリアル化し、次に配列自体をシリアル化します。これは私がデシリアライズするためにしたことです:

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.bin"));
int size = ois.readInt(); // read size of the map
HashMap<String, Double> newMap = new HashMap<>(size);
for (int i = 0; i < size; i++) { // iterate entries
    int length = ois.readInt(); // length of key string
    byte[] bytes = new byte[length];
    ois.read(bytes); // key string bytes
    String key = new String(bytes);
    double value = ois.readDouble(); // value
    newMap.put(key, value);
    System.out.println("read ("+ key +","+ value +")");
}

問題は、ある時点でキーが正しくシリアル化されないことです。本来の 16 バイトではなく 8 バイトを読み取ることができるようになるまでデバッグを行ったois.read(bytes)ため、キーStringが適切に形成されてdoubleおらず、まだ読み取られていないキーの最後の 8 バイトを使用して値が読み取られました。 . 結局、例外はどこにでもあります。

以下のサンプル データを使用すると、出力はある時点で次のようになります。

read (2010-00-056.html,12154.250518054876)
read (2010-00-        ,1.4007397428546247E-76)
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at ti.Test.main(Test.java:82)

この問題は、シリアル化されたファイルで確認できます (次のように表示されます2010-00-008.html)。

ここに画像の説明を入力

キーの間に 2 バイトが追加されStringます。詳細については、 MxyL の回答を参照してください。要するに、なぜこれらの 2 バイトが追加され、なぜ正常にreadFully動作するのかということです。

String適切に (デ) シリアライズされないのはなぜですか? 固定ブロックサイズへのある種のパディングか、そのようなものでしょうか? 効率を求めるときに手動でシリアル化するより良い方法はありますか? Stringある種のwriteStringandを期待していreadStringましたが、Java の にはそのようなものはないようですObjectStream

何か問題が発生した場合に備えて、バッファリングされたストリームを使用してみました。さまざまなエンコーディングを使用して、書き込みと読み取りのバイト数を明示的に示していますが、うまくいきません。

これは、問題を再現するためのサンプル データです。

HashMap<String, Double> map = new HashMap<String, Double>();
map.put("2010-00-027.html",21732.994621513037); map.put("2010-00-020.html",3466.5169348296736); map.put("2010-00-051.html",12528.648992702407); map.put("2010-00-062.html",3354.8950010256385);
map.put("2010-00-024.html",10295.095511718278); map.put("2010-00-052.html",5381.513344679818);  map.put("2010-00-007.html",16466.33813960735);  map.put("2010-00-017.html",9484.969198176652);
map.put("2010-00-054.html",15423.873112634772); map.put("2010-00-022.html",8123.842752870753);  map.put("2010-00-033.html",21238.496665104063); map.put("2010-00-028.html",7578.792651786424);
map.put("2010-00-048.html",3566.4118233046393); map.put("2010-00-040.html",2681.0799941861724); map.put("2010-00-049.html",14308.090890746222); map.put("2010-00-058.html",5911.342406606804);
map.put("2010-00-045.html",2284.118716145881);  map.put("2010-00-031.html",2859.565771680721);  map.put("2010-00-046.html",4555.187022907964);  map.put("2010-00-036.html",8479.709295569426);
map.put("2010-00-061.html",846.8292195815125);  map.put("2010-00-023.html",14108.644025417952); map.put("2010-00-041.html",22686.232732684934); map.put("2010-00-025.html",9513.539663409734);
map.put("2010-00-012.html",459.6427911376829);  map.put("2010-00-005.html",0.0);    map.put("2010-00-013.html",2646.403220496738);  map.put("2010-00-065.html",5808.86423609936);
map.put("2010-00-056.html",12154.250518054876); map.put("2010-00-008.html",10811.15198506469);  map.put("2010-00-042.html",9271.006516004005);  map.put("2010-00-000.html",4387.4162586468965);
map.put("2010-00-059.html",4456.211623469774);  map.put("2010-00-055.html",3534.7511584735325); map.put("2010-00-057.html",8745.640098512009);  map.put("2010-00-032.html",4993.295735075575);
map.put("2010-00-021.html",3852.5805998017922); map.put("2010-00-043.html",4108.020033536286);  map.put("2010-00-053.html",2.2446400279239946); map.put("2010-00-030.html",17853.541210836203);
4

4 に答える 4

2
ois.read(bytes); // key string bytes

これを readFully() を使用するように変更します。読み取りでバッファがいっぱいになったと想定しています。複数のバイトを転送する義務はありません。

効率を求めるときに文字列を手動でシリアル化するより良い方法はありますか?

writeUTF() と readUTF() のペアがあります。

getBytes() を呼び出すと、プラットフォームの依存関係が導入されることに注意してください。ここと文字列を再構築するときの両方で文字セットを指定する必要があります。

于 2014-05-30T00:35:28.967 に答える
1

ここで注意すべき点が 2 つあります

まず、サンプル データの最後の 4 つのエントリを取り出した場合、エラーは発生しません。つまり、2 バイトが誤って追加されることはありません。変。

次に、16 進エディタでファイルを開き、余分な 2 バイトが発生するエントリまで下にスクロールすると、4 バイトの整数で始まることがわかりますが、これは正しくは 16 の値です (これはビッグエンディアン)。次に、2 つの余分なバイトを含む文字列が表示され、その後に関連付けられた double が続きます。

ここで奇妙なのは、Java がこれらのバイトを読み取る方法です。まず、指示に従って文字列の長さを読み取ります。次に、16バイトの読み取りを試みます...しかし、ここでは、印刷ステートメントが表示されるため、16バイトの読み取りに失敗したようです

read (2010-00-,1.3980409401811577E-76))

これらの 2 つの奇妙なバイトの直後にカーソルを置くと、次のように表示されます。

画像

文字列の開始位置から現在のポインターの位置まで、10 バイトしか読み取っていないようです。

さらに、IDEのコンソールからその行をコピーしようとすると、貼り付けられるだけでした

read (2010-00-

通常、コピーペーストで文字列が突然終了する場合、通常はヌルバイトが疑われます。実際、私のクリップボードを見ると、バイトがバッファに完全に読み込まれていないようです:

img2

わかりました。Java は 10 バイトしか読み取れず、先に進んだようです。これにより、文字列と数値が後で説明されます。

そのためread、バッファーを渡しても、完全に満たされていないように見えます。を使用するように指示するツールチップ自体からの推奨事項さえありますreadFully!

img4

少しテストを行って、先に進んで変更しました

ois.read(bytes); // key string bytes

ois.readFully(bytes, 0, length); // key string bytes

そして、何らかの理由で、これは機能します。

read (2010-00-013.html,2646.403220496738)
read (2010-00-005.html,0.0)
read (2010-00-056.html,12154.250518054876)
read (2010-00-008.html,10811.15198506469)
read (2010-00-042.html,9271.006516004005)
read (2010-00-000.html,4387.4162586468965)  // where it was failing before
read (2010-00-059.html,4456.211623469774)

問題

さて、それが実際に機能したという事実が問題です。なぜそれが機能するのですか?文字列の間に 2 つの余分なバイトがあることは明らかです (文字列の長さは 16 ではなく 18 になります)。ファイルが変更されたようなものではありません。

実際、ファイルを手動で編集してエントリが 3 つだけになるようにし、エントリが 2 つしかないことを示すと、次のような出力が得られます。

img3

read (2010-00-056.html,12154.250518054876)
read (2010-00-wd008.ht,1.2466701288348126E219)

これは、18 バイトの文字列に期待するものです (そうではないかもしれませんがwd、私は期待w,していました) が、16 バイトしかないと指定しましたreadFully

だから謎が多い

  1. これらの 2 つの余分なバイトが追加される理由
  2. 最後の 4 つのエントリ (または必要に応じてそれ以上) を削除しても、それらが追加されないのはなぜですか?
  3. using が機能するのはなぜreadFullyですか? それ以外はすべて一定です。

残念ながら、この回答はあなたの質問に答えていません。また、あなたが提起した問題だけでなく、私が目にしている行動にもかなり困惑しています。

于 2014-05-30T00:26:04.253 に答える
-1

ObjectInputStream#readは、 buffer.length() バイト数を読み取ることを保証しません。現在の先読みバッファ ブロックの端で読み取りが発生すると、バッファに残っているバイト数のみが返されます。このように書くべきです。

        int offset=0;
        while(offset<length) {
            int cnt=ois.read(bytes,offset, length-offset); // key string bytes
            offset+=cnt;
        }
于 2014-05-29T23:54:06.450 に答える