Compress your data

How to compress data not found in a file

December 7, 2002

Q: This week, I answer two questions concerning data compression in Java.

Question 1:

How can I zip something that isn't in a file?

Question 2:

I read with enthusiasm Todd Sundsted's "Zip Your Data and Improve the Performance of Your Network-Based Applications," (JavaWorld, November 1998) but I was a little disappointed.

When I read the title of the article, I was ecstatic. I thought I had finally found the solution for our problem.

At my company, we have been trying to improve the performance of an RMI (Remote Method Invocation) application that organizes data. The server does most of the process. We've made performance improvements in the past year and a half, but the bottleneck now is the data transfer. At any point in the day, we might be transferring thousands of records between the client and the server. As one possible solution, I proposed we compress the data before returning it to the client, a process I thought Todd's article would address. However, the example given in his article compresses files, not data, as we need.

On the RMI implementation, we get the results from the database, put them in a list, and return the list to the client, which in turn puts the results in a JTable. I would like to compress that list before sending it back to the client, decompress it at the client, then insert the data in the JTable.

Can this be done?

A: Note: You can download this article's source code from Resources.

In regards to question 1, nothing forces you to use files when you use ZipInputStream and ZipOutputStream. The only stipulation is that you convert your data to byte arrays.

Question 2, however, proves more involved because it requires a change in the way RMI communicates over the wire. In order to make RMI compress its data before sending it over the wire, you must create a new socket type that compresses the data. Then, once you have created the socket, you must tell RMI to use it.

"Creating a Custom RMI Socket Factory," a tutorial from Sun Microsystems, enumerates the steps necessary to create a new socket and get RMI to use it.

Briefly, to change RMI's socket type:

  1. Choose or create a new socket type. (See Sun's "Creating a Custom Socket Type," to learn more.)
  2. Create a ServerSocket.
  3. Create a RMIClientSocketFactory.
  4. Create a RMIServerSocketFactory.
  5. Make the constructor of the remote object that extends UnicastRemoteObject use the new factories.

Following the scenario outlined in question 2 above, let's go through each step for zip compression.

Step 1: Create the ZipSocket

In the case of zip compression, create the following socket:

 
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import java.net.Socket;
public class ZipSocket extends Socket {
    private InputStream in;
    private OutputStream out;
    
    public ZipSocket() { super(); }
    public ZipSocket(String host, int port) 
        throws IOException {
            super(host, port);
    }
    
    public InputStream getInputStream() 
        throws IOException {
            if (in == null) {
                in = new ZipInputStream(super.getInputStream());
            }
  return in;
    }
 
    public OutputStream getOutputStream() 
        throws IOException {
            if (out == null) {
                out = new ZipOutputStream(super.getOutputStream());
            }
            return out;
    }
    
    public synchronized void close() throws IOException {
        OutputStream o = getOutputStream();
        o.flush();
        super.close();
    }
}

Step 2: Create the ZipServerSocket

Create the following server socket:

import java.net.ServerSocket;
import java.net.Socket;
import java.io.IOException;
public class ZipServerSocket extends ServerSocket
{
  public ZipServerSocket(int port) throws IOException {    
    super(port);
  }
  
  public Socket accept() throws IOException { 
    Socket socket = new ZipSocket();
    implAccept(socket);
    return socket;
  }
}

Step 3: Create a ZipClientSocketFactory

The client factory will need to take the following form:

import java.io.IOException; 
import java.io.Serializable; 
import java.net.Socket; 
import java.rmi.server.RMIClientSocketFactory;
public class ZipClientSocketFactory 
    implements RMIClientSocketFactory, Serializable { 
    public Socket createSocket(String host, int port) 
        throws IOException { 
            ZipSocket socket = new ZipSocket(host, port); 
            return socket; 
    } 
}

Step 4: Create a ZipServerSocketFactory

Here's the server factory code:

import java.io.IOException; 
import java.io.Serializable; 
import java.net.ServerSocket; 
import java.rmi.server.RMIServerSocketFactory; 
  
public class ZipServerSocketFactory 
    implements RMIServerSocketFactory, Serializable { 
    public ServerSocket createServerSocket(int port) 
        throws IOException { 
            ZipServerSocket server = new ZipServerSocket(port); 
            return server; 
    } 
}

Step 5: Make the constructor of the remote object that extends UnicastRemoteObject use the new factories

Here's the code:

public class YourRMIObject extends UnicastRemoteObject {
    
    public YourRemoteObject( int port ) {
        super( port, new ZipClientSocketFactory(), new ZipServerSocketFactory() );
    }
    // the rest of your implementation
}

Now your communication will be compressed.

Tony Sintes is an independent consultant and founder of First Class Consulting, Inc., a consulting firm that specializes in the bridging of disparate enterprise systems and training. Outside of First Class Consulting, Tony is an active freelance writer as well as author of Sams Teach Yourself Object-Oriented Programming in 21 Days (Sams, 2001; ISBN: 0672321092).

Learn more about this topic