【问题标题】:Compress Java HashMap to be sent over RMI压缩要通过 RMI 发送的 Java HashMap
【发布时间】:2010-11-25 16:04:29
【问题描述】:

我们有一个通过 RMI 进行通信的客户端/服务器应用程序。服务器向客户端发送 HashMap。一切正常,但是在发送大型 HashMap 时,传输时间可能会很慢。

有什么方法可以在发送前压缩HashMap,然后在客户端解压?我不想在磁盘上创建任何文件(所有文件都必须在 RAM 中)

谢谢

【问题讨论】:

  • 您在地图中放置了什么样的对象,地图中的平均元素数量是多少?
  • 数据增长缓慢不正常吗?我的意思是,如果一个包含 100 个元素的地图需要 100 毫秒,那么一个包含大约 200 个元素的地图不会花费大约 200 毫秒(粗略地说)。你有任何数字表明这种缓慢吗?我宁愿拆分请求并将其分批发送到服务器,而不是出于性能原因乱搞代码,除非绝对没有办法并且您的问题不允许拆分请求...

标签: java compression hashmap rmi


【解决方案1】:

您可以将 DeflatorOutputStream 用于 ByteArrayOutputStream,但是您最终会得到一个 byte[],因此您的 RMI 调用应该返回一个 byte[]。

小型可序列化对象不会很好地压缩,但是如果您有许多可序列化对象,它可以很好地压缩。大量文本也可以。

最简单的方法就是尝试一下。如果有重复的字符串甚至部分字符串,这将有助于压缩。

public static void main(String... args) throws IOException {
    Map<String, String> map = new HashMap<String, String>();

    for(int i=0;i<1000;i++)
        map.put(""+Math.random(), ""+Math.random());
    byte[] bytes1 = toBytes(map);
    byte[] bytes2 = toCompressedBytes(map);
    System.out.println("HashMap with "+map.size()+" entries, Uncompressed length="+bytes1.length+", compressed length="+bytes2.length);
}

public static byte[] toCompressedBytes(Object o) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(new DeflaterOutputStream(baos));
    oos.writeObject(o);
    oos.close();
    return baos.toByteArray();
}

public static byte[] toBytes(Object o) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(baos);
    oos.writeObject(o);
    oos.close();
    return baos.toByteArray();
}

public static Object fromCompressedBytes(byte[] bytes) throws IOException, ClassNotFoundException {
    ObjectInputStream ois = new ObjectInputStream(new InflaterInputStream(new ByteArrayInputStream(bytes)));
    return ois.readObject();
}

打印

HashMap with 1000 entries, Uncompressed length=42596, compressed length=19479

【讨论】:

  • 为什么要使用中间的 ByteArrayOutputStream?
  • 获取 toByteArray()。还有另一种获取字节[]的方法吗?
  • 在这里查看我的答案,它不使用 ByteArrayOutputStream。只有当你想要统计数据时它才真正有用。 stackoverflow.com/a/41550311/1247302
【解决方案2】:

不要对 hashmap 做任何事情。相反,Write a custom socket factory 使用 DeflaterOutputStream 压缩数据。

【讨论】:

    【解决方案3】:

    很多年前,我曾经将对象序列化为字节数组,然后将其压缩。 Java 仍然支持 Zip :) 所以试试这个方法。

    【讨论】:

      【解决方案4】:

      您可以为哈希图中的元素尝试custom serialization mechanism

      您要发送什么样的信息?里面的物体是什么样子的?

      即使使用默认机制,并将所有不需要的属性标记为瞬态也会有所帮助。

      此外,您可以尝试将您自己序列化的数据发送到ZipOutputStream,但我会将其作为最后一个资源,因为二进制内容不会压缩太多。

      编辑

      由于您只使用字符串,您可以创建一个包装器,其自定义序列化是一个压缩数组(就像 Peter Lawrey 的回答一样),但是,使用自定义序列化可以让您封装序列化过程并让它以某种方式“透明地工作” " 用于 RMI(RMI 序列化永远不会知道您使用的是压缩版本)

      这是一个演示:

      import java.io.*;
      import java.util.*;
      import java.util.zip.*;
      
      public class MapDemo implements Serializable { 
      
          private Map<String,String> map = new HashMap<String,String>();
          // only for demo/comparison purposes, default would use compressoin always
          private boolean useCompression;
          public MapDemo( Map<String,String> map , boolean compressed ) { 
              this.map = map;
              this.useCompression = compressed;
          }
      
         // This is the custom serialization using compression 
         private void writeObject(ObjectOutputStream out) throws IOException {
           ByteArrayOutputStream baos = new ByteArrayOutputStream();
      
           OutputStream os = useCompression ?  new DeflaterOutputStream( baos ) : baos;
      
           ObjectOutputStream oos     = new ObjectOutputStream(  os );
           oos.writeObject( this.map  );
           oos.close();
      
           out.write( baos.toByteArray() );
         }
      }
      
      class Main { 
          public static void main( String [] args )  throws IOException { 
              Map<String,String> regular    = new HashMap<String,String>();
              Map<String,String> compressed = new HashMap<String,String>();
              Random r = new Random();
              for( int i = 0 ; i < 100000 ; i++ ) { 
                  String key      = ""+r.nextInt(1000000);
                  String value    = ""+r.nextInt(1000000) ;
                  // put the same info 
                  compressed.put( key , value );
                  regular.put( key , value );
              }   
              save( new MapDemo( compressed, true ) , "map.compressed");
              save( new MapDemo( regular, false ) , "map.regular");
          }
          private static void save( Object o, String toFile ) throws IOException  { 
              // This is similar to what RMI serialization would do behind scenes
              ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream(toFile));
              oos.writeObject( o );
              oos.close();
          }
      
      }
      

      【讨论】:

      • 在将它们添加到地图之前尝试调用“string.intern()”。希望这会减少地图中的对象数量并简化序列化过程。
      • 我想知道实习如何提高序列化性能,因为无论如何都需要将字符串发送到另一个 jvm
      • 如果只有字符串,那么@Peter Layer's 是最佳解决方案
      • @Pangea 希望使用实习生 100 个重复的字符串将被序列化为仅 1。
      • 是否有任何算法可以压缩到 90-95%,Java 也为此提供了实现。
      猜你喜欢
      • 2015-01-06
      • 1970-01-01
      • 1970-01-01
      • 2019-02-09
      • 2011-01-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多