Javaで大量データをメモリに展開するテクニックの考察

Mapに突っ込んだプロパティ的なデータをさらにListに突っ込む。
みたいな処理を行いたいとします。

Listに登録するデータ量がそれなりにある場合、すぐにOutOfMemoryが
発生してしまうので、なんとかならないか考えてみました。



通常パターン



何も考えずに作ったプログラムがこちら。
Mapにkey-valueのペアを100個ほど登録し、さらにそのMapをListに追加します。

-Xmx10mのオプションをつけて、使用するメモリを10MBに制限して実行し、
何個Listに突っ込めるか試してみました。


  1. package sample;
  2. import java.util.ArrayList;
  3. import java.util.HashMap;
  4. import java.util.List;
  5. import java.util.Map;
  6. public class MainProcess {
  7.     
  8.     // Listに何個ダミーのHashMapのデータが登録できるかテスト
  9.     // -Xmx10mのオプションをつけて実行
  10.     public static void main(String[] args) {
  11.         
  12.         List<Map<String, String>> list = new ArrayList<Map<String, String>>();
  13.         
  14.         int count = 0;
  15.         try {
  16.             while(true){
  17.                 list.add(createDataMap());
  18.                 count++;
  19.             }
  20.         } catch (OutOfMemoryError e) {
  21.             list = null;
  22.             System.out.println(count);
  23.         }
  24.     }
  25.     
  26.     // テスト用に100個のkey-valueペアを持つHashMapを生成
  27.     private static Map<String, String> createDataMap() {
  28.         
  29.         Map<String ,String> map = new HashMap<String, String>();
  30.         
  31.         for (int i = 0; i < 100; i++) {
  32.             map.put("key-" + Integer.toString(i), "value-" + Integer.toString(i));
  33.         }
  34.         
  35.         return map;
  36.     }
  37. }




結果は、私の環境では「476」となりました。









シリアライズしてみる



Listにオブジェクトのまま突っ込むのではなく、シリアライズしてbyteの配列にした後、
Listに突っ込むように変更してみます。


  1. package sample;
  2. import java.io.ByteArrayInputStream;
  3. import java.io.ByteArrayOutputStream;
  4. import java.io.IOException;
  5. import java.io.ObjectInputStream;
  6. import java.io.ObjectOutputStream;
  7. import java.util.ArrayList;
  8. import java.util.HashMap;
  9. import java.util.List;
  10. import java.util.Map;
  11. public class MainProcess {
  12.     // Listに何個ダミーのHashMapのデータが登録できるかテスト
  13.     // -Xmx10mのオプションをつけて実行
  14.     public static void main(String[] args) {
  15.         List<byte[]> list = new ArrayList<byte[]>();
  16.         int count = 0;
  17.         try {
  18.             while (true) {
  19.                 list.add(toByte(createDataMap()));
  20.                 count++;
  21.             }
  22.         } catch (OutOfMemoryError e) {
  23.             list = null;
  24.             System.out.println(count);
  25.         }
  26.         
  27.         //正しく戻せるかテスト
  28.         byte[] test = toByte(createDataMap());
  29.         Map<String, String> map = (Map<String, String>) expandObject(test);
  30.         System.out.println(map.get("key-50"));
  31.         
  32.     }
  33.     // テスト用に100個のkey-valueペアを持つHashMapを生成
  34.     private static Map<String, String> createDataMap() {
  35.         Map<String, String> map = new HashMap<String, String>();
  36.         for (int i = 0; i < 100; i++) {
  37.             map.put("key-" + Integer.toString(i), "value-" + Integer.toString(i));
  38.         }
  39.         return map;
  40.     }
  41.     
  42.     //オブジェクトをシリアライズし、byteの配列を得る
  43.     public static byte[] toByte(Object obj) {
  44.         ByteArrayOutputStream bos = null;
  45.         try {
  46.             //オブジェクトをシリアライズし、バイト配列にする
  47.             bos = new ByteArrayOutputStream();
  48.             ObjectOutputStream oos = new ObjectOutputStream(bos);
  49.             oos.writeObject(obj);
  50.             byte b[] = bos.toByteArray();
  51.             return b;
  52.             
  53.         } catch (Throwable throwable) {
  54.             return null;
  55.         } finally {
  56.             if (bos != null) {
  57.                 try {
  58.                     bos.close();
  59.                 } catch (IOException e) {
  60.                 }
  61.                 bos = null;
  62.             }
  63.         }
  64.     }
  65.     
  66.     //byte配列からオブジェクトに復元する
  67.     public static Object expandObject(byte b[]) {
  68.         
  69.         try {
  70.             ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(b));
  71.             return ois.readObject();
  72.         } catch (Exception e) {
  73.             return null;
  74.         }
  75.     }
  76. }




結果は「4616」になりました。
約10倍のデータがメモリに展開できたことになります。







シリアライズ + zip圧縮



シリアライズして得られたbyte配列をさらにzip圧縮してやります。


  1. package sample;
  2. import java.io.ByteArrayInputStream;
  3. import java.io.ByteArrayOutputStream;
  4. import java.io.IOException;
  5. import java.io.ObjectInputStream;
  6. import java.io.ObjectOutputStream;
  7. import java.util.ArrayList;
  8. import java.util.HashMap;
  9. import java.util.List;
  10. import java.util.Map;
  11. import java.util.zip.GZIPInputStream;
  12. import java.util.zip.GZIPOutputStream;
  13. public class MainProcess {
  14.     // Listに何個ダミーのHashMapのデータが登録できるかテスト
  15.     // -Xmx10mのオプションをつけて実行
  16.     public static void main(String[] args) {
  17.         List<byte[]> list = new ArrayList<byte[]>();
  18.         int count = 0;
  19.         try {
  20.             while (true) {
  21.                 list.add(toZipByte(createDataMap()));
  22.                 count++;
  23.             }
  24.         } catch (OutOfMemoryError e) {
  25.             list = null;
  26.             System.out.println(count);
  27.         }
  28.         
  29.         //正しく戻せるかテスト
  30.         byte[] test = toZipByte(createDataMap());
  31.         Map<String, String> map = (Map<String, String>) expandObject(test);
  32.         System.out.println(map.get("key-50"));
  33.         
  34.     }
  35.     // テスト用に100個のkey-valueペアを持つHashMapを生成
  36.     private static Map<String, String> createDataMap() {
  37.         Map<String, String> map = new HashMap<String, String>();
  38.         for (int i = 0; i < 100; i++) {
  39.             map.put("key-" + Integer.toString(i), "value-" + Integer.toString(i));
  40.         }
  41.         return map;
  42.     }
  43.     
  44.     
  45.     
  46.     
  47.     //オブジェクトをシリアライズし、zip圧縮したbyteの配列を得る
  48.     public static byte[] toZipByte(Object obj) {
  49.         ByteArrayOutputStream bos = null;
  50.         GZIPOutputStream gos = null;
  51.         try {
  52.             //オブジェクトをシリアライズし、バイト配列にする
  53.             bos = new ByteArrayOutputStream();
  54.             gos = new GZIPOutputStream(bos);
  55.             
  56.             ObjectOutputStream oos = new ObjectOutputStream(gos);
  57.             oos.writeObject(obj);
  58.             gos.finish();
  59.             return bos.toByteArray();
  60.             
  61.         } catch (Throwable throwable) {
  62.             return null;
  63.         } finally {
  64.             if (bos != null) {
  65.                 try {
  66.                     bos.close();
  67.                 } catch (IOException e) {
  68.                 }
  69.                 bos = null;
  70.             }
  71.             
  72.             if (gos != null) {
  73.                 try {
  74.                     gos.close();
  75.                 } catch (IOException e) {
  76.                 }
  77.                 gos = null;
  78.             }
  79.         }
  80.     }
  81.     
  82.     //zipのbyte配列からオブジェクトに復元する
  83.     public static Object expandObject(byte b[]) {
  84.         
  85.         ByteArrayInputStream bis = null;
  86.         ByteArrayOutputStream bos = null;
  87.         GZIPInputStream gis = null;
  88.         
  89.         try {
  90.             
  91.             bis = new ByteArrayInputStream(b);
  92.             gis = new GZIPInputStream(bis);
  93.             bos = new ByteArrayOutputStream();
  94.             
  95.             int length = 0;
  96.             byte[] tmp = new byte[4096];
  97.             while((length = gis.read(tmp, 0, tmp.length)) != -1) {
  98.                 bos.write(tmp, 0, length);
  99.             }
  100.             
  101.             byte[] bobj = bos.toByteArray();
  102.             
  103.             ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bobj));
  104.             return ois.readObject();
  105.         } catch (Exception e) {
  106.             return null;
  107.             
  108.         } finally {
  109.             if (bis != null) {
  110.                 try {
  111.                     bis.close();
  112.                 } catch (Exception e) {
  113.                 }
  114.                 bis = null;
  115.             }
  116.             
  117.             if (bos != null) {
  118.                 try {
  119.                     bos.close();
  120.                 } catch (Exception e) {
  121.                 }
  122.                 bos = null;
  123.             }
  124.             
  125.             if (gis != null) {
  126.                 try {
  127.                     gis.close();
  128.                 } catch (Exception e) {
  129.                 }
  130.                 gis = null;
  131.             }
  132.         }
  133.     }
  134. }




結果は「16573」になりました。
シリアライズのみの場合の約3.5倍。
オブジェクトをそのまま格納した時の約35倍になりました。







まとめ



結果をまとめると、このようになります。
かなり単純なサンプルデータですが、一般的なデータにも同様の傾向が
みられるんじゃないかと思っています。

パターンListに入った個数
通常476
シリアライズ4616
シリアライズ + zip16573



ちなみに、Mapをバイト配列に変換していますが、
シリアライズのみ:2062byte
シリアライズ + zip:565byte
でした。


あんまり使い所はないですが、使用メモリが逼迫しているときには
使えるかもしれません。


関連記事

コメント

非公開コメント

プロフィール

Author:symfo
blog形式だと探しにくいので、まとめサイト作成中です。
Symfoware まとめ

PR




検索フォーム

月別アーカイブ