Compress Java HashMap to be sent over RMI
We have开发者_JAVA百科 a Client/Server application which communicates over RMI. The server sends HashMaps to the client. All works well, however when sending large HashMaps, transfer times can be slow.
Is there any way to compress the HashMaps before sending, then decompress on the client? I do not want to create any files on disk whatsoever (All must be in RAM)
Thanks
You can use DeflatorOutputStream to a ByteArrayOutputStream, however you will end up with a byte[] so your RMI call should return a byte[].
Small serializable obejct won't compress well, however if you have many Serializable objects it can compress very well. So can large amounts of text.
The simplest thing to do is to try it. If there are repeated strings or even portions of strings, this will help compression.
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();
}
Prints
HashMap with 1000 entries, Uncompressed length=42596, compressed length=19479
Don't do anything to the hashmap. Instead, Write a custom socket factory that compresses the data using a DeflaterOutputStream.
Many years ago I used to serialize objects into byte array and then zip it. Zip is still supported by Java :) so try this method.
You may try a custom serialization mechanism for the elements inside the hashmap.
What kind of information are you sending? how do the object inside look like?
Even using the default mechanism, and marking all the unneeded attributes as transient will help.
Additionally you may attempt to sending the data your self serializing it before to a ZipOutputStream but I would let that as a last resource, for the binary content won't compress too much.
EDIT
Since your using only strings, you can create an wrapper whose custom serialization is a compressed array ( pretty much as Peter Lawrey answer ) but, using a custom serialization would let you encapsulate the serialization process and have it working some how "transparently" for RMI ( RMI serialization would never know you're using a compressed version )
Here's a demo:
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();
}
}
精彩评论