2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2020

04/22/2004: Java; POI Optimization - Speeding up the RecordFactory class.

This optimization needs two posts. This post presents a class that maps short values to a Constructor object.

The RecordFactory class maps the short id value in the Excel file to a Java class. In the current version of POI, that relationship is kept, quite logically, in a Map object. However, using a Map object required that the short value be converted into a Short object quite frequently. This conversion is inefficient and, if some specially coding is done, unneeded. Note that the following class, shortToConstructorCache, is derivative of my earlier shortShortCache class.</p>

/**
 * Cache a mapping from a short value to a Constructor object.
 *
 * This class is designed to optimize the RecordFactory.createRecord()
 * method. It might be useful in other contexts, but I did not check.
 */
public class shortToConstructorCache {

    /**
     * The number of entries in the cache.
     */
    protected int       distinct;

    /**
     * The cached short values.
     */
    private short       table[];

    /**
     * The cached constructor methods
     */
    private Constructor values[];

    /**
     * RecordFactory uses a statically created array of
     * classes to initialize the cache and the entries
     * in the cache are never changed nor added to.
     */
    public shortToConstructorCache(Class[] records) {
        super();

    	this.table = new short[records.length];
    	this.values = new Constructor[records.length];

        Constructor constructor;

        for (int i = 0; i < records.length; i++) {
            Class record = null;
            short sid = 0;

            record = records[i];
            try {
                sid = record.getField("sid").getShort(null);
                constructor = record.getConstructor(new Class[] {short.class, short.class, byte[].class});
            } catch (Exception illegalArgumentException) {
                illegalArgumentException.printStackTrace();
                throw new RecordFormatException("Unable to determine record types");
            }

            if (constructor == null) {
                throw new RecordFormatException("Unable to get constructor for sid [" + sid + "].");
            } else {
                this.table[this.distinct] = sid;
                this.values[this.distinct] = constructor;
                this.distinct++;
            }
        }
    }

    /** Gets the Constructor object related to a given
     * key.
     */
    public Constructor get(short key) {
        Constructor rv = null;

        	for (int i = 0; i < this.distinct; i++) {
        	    if (this.table[i] == key) {
        	        rv = this.values[i];
        	    }
        	}

        return rv;
    }

    /** Returns the number of entries in the cache. */
    public int size() {
        return this.distinct;
    }

    /** We're breaking encapsulation but some code in RecordFactory
     * wants the information and I want to change RecordFactory as
     * little as possible.
     */
    public short[] getTable() {
        return this.table;
    }

}

04/22/2004: Java; Caching Short Objects

While looking at the POI source code, I noticed that a lot of Short objects were being created. So I looked around for a small stand-alone class that would allow me to cache Short object.

I did see some pages devoted to sparse arrays and matrixes (notably the COLT package) however they were too large for my purposes.

I wrote the shortShortCache class shown below. It usage should be pretty obvious but please contact me if you have any difficulty using the class or suggestions for improvement.

/**
 * Provides a cache for Short objects. This class should be used when the same short values
 * are used over and over to avoid the cost of continously creating the same Short objects.
 *
 * Since the get() method creates Short objects and adds them to the cache, the put()
 * method never needs to be called outside this class.
 */

public class shortShortCache {

    /** This is how the cache is used. */
    public static void main(String[] args) {
        shortShortCache ssm = new shortShortCache();
        short numEntries = (short) 2000;
        short start = (short) 23454;
        short middle = (short) (start + (numEntries / 2));
        short end = (short) (start + numEntries);
        for (short i = start; i <= end; i++) {
            ssm.put(i);
        }
        // is the first short cached?
        System.out.println(start + ": " + ssm.get(start));
        // is the middle short cached?.
        System.out.println(middle + ": " + ssm.get(middle));
        // is the last short cached?
        System.out.println(end + ": " + ssm.get(end));
        System.out.println("Done.");
    }

    /** The initalize size of the cache. */
    private int   initialSize     = 500;

    /** How much to grow the cache when its capacity is reached. */
    private int   increment       = 500;

    /** The size of the cache, which is not the same as the number of entries in the caceh. */
    private int   currentCapacity = 0;

    /**
     * The number of entries in the cache.
     */
    protected int distinct        = 0;

    /** The maximum number of entries so that the cache doesn't grow unbounded. */
    protected int maxEntries      = 3000;

    /**
     * The cached short values.
     */
    private short table[];

    /**
     * The cached Short methods
     */
    private Short values[];

    /** A no-args constructor which uses all of the defaults. */
    public shortShortCache() {
        super();
        clear();
    }

    /** A constructor that lets the user set the control variables. */
    public shortShortCache(final int _initialSize, final int _increment, final int _maxEntries) {
        super();
        this.initialSize = _initialSize;
        this.increment = _increment;
        // we quietly handle the error of a _maxEntries parameter less than the _initialSize parameter.
        if (_maxEntries < _initialSize) {
            this.maxEntries = _initialSize;
        } else if (_maxEntries > Short.MAX_VALUE) {
            this.maxEntries = Short.MAX_VALUE;
        } else {
            this.maxEntries = _maxEntries;
        }
        clear();
    }

    /** Create and/or clear the cache. */
    public void clear() {
        this.table = new short[this.initialSize];
        this.values = new Short[this.initialSize];
        this.currentCapacity = this.initialSize;
        this.distinct = 0;
    }

    /**
     * Returns the value to which this map maps the specified key. If the short value
     * is not in the cache, then create a Short object automatically.
     */
    public Short get(short key) {
        Short rv = null;

        for (int i = 0; i < this.distinct; i++) {
            if (this.table[i] == key) {
                rv = this.values[i];

            }
        }

        // If the key is not in the cache, then add it
        // to the cache.
        if (rv == null) {
            rv = new Short(key);
            if (this.currentCapacity < this.maxEntries) {
                put(key);
            }
        }

        return rv;
    }

    /**
     * Add a mapping from a short to a Short object.
     *
     * If the size of the cache is too small, then expand it. If the size of the cache
     * is more than the maxEntries, then return. The get() method automatically
     * creates a Short object for any short values not in the cache.
     */
    public void put(short key) {
        if (this.currentCapacity < this.maxEntries) {
            if (this.distinct == this.currentCapacity) {
                int newCapacity = this.currentCapacity + this.increment;

                // store the current cache.
                short oldTable[] = this.table;
                Short oldValues[] = this.values;

                // create new arrays.
                short newTable[] = new short[newCapacity];
                Short newValues[] = new Short[newCapacity];

                this.table = newTable;
                this.values = newValues;

                // move info from old table to new table.
                for (int i = this.currentCapacity; i-- > 0;) {
                    newTable[i] = oldTable[i];
                    newValues[i] = oldValues[i];
                }

                this.currentCapacity = newCapacity;
            }
            this.table[this.distinct] = key;
            this.values[this.distinct] = new Short(key);
            this.distinct++;
        }
    }

    /** Returns the number of key-value mappings in this map. */
    public int size() {
        return this.distinct;
    }

    /**
     * Returns true if this map contains a mapping for the specified key.
     */
    public boolean containsKey(short key) {
        boolean rv = false;

        for (int i = 0; i < this.distinct; i++) {
            if (this.table[i] == key) {
                rv = true;
                break;
            }
        }
        return rv;
    }

    /**
     * Returns true if this map maps one or more keys to the specified value.
     */
    public boolean containsValue(Short value) {
        boolean rv = false;

        if (value != null) {
            for (int i = 0; i < this.distinct; i++) {
                if (this.values[i].equals(value)) {
                    rv = true;
                    break;
                }
            }
        }
        return rv;
    }

    /**
     * Returns true if this map contains no key-value mappings.
     */
    public boolean isEmpty() {
        boolean rv = false;
        if (this.distinct > 0) {
            rv = true;
        }
        return rv;
    }

    /**
     * From http://www.ftponline.com/javapro/2004_03/online/rule4_03_31_04/:
     *
     * Java's object cloning mechanism can allow an attacker to manufacture new
     * instances of classes that you define�without executing any of the class's
     * constructors. Even if your class is not cloneable, the attacker can define a
     * subclass of your class, make the subclass implement java.lang.Cloneable,
     * and then create new instances of your class by copying the memory images
     * of existing objects. By defining this clone method, you will prevent such attacks.
     */
    public final Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }

    /**
     * List all short values in the cache.
     */
    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("shortShortMap[" + this.distinct + "]: ");
        for (int i = 0; i < this.distinct; i++) {
            sb.append(this.table[i] + ", ");
        }
        return sb.toString();
    }
}