Mega Code Archive

 
Categories / Java / Collections Data Structure
 

Type-safe Map, from char array to String value

/* Woodstox XML processor  *  * Copyright (c) 2004- Tatu Saloranta, tatu.saloranta@iki.fi  *  * Licensed under the License specified in the file LICENSE which is  * included with the source code.  * You may not use this file except in compliance with the License.  *  * Unless required by applicable law or agreed to in writing, software  * distributed under the License is distributed on an "AS IS" BASIS,  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  * See the License for the specific language governing permissions and  * limitations under the License.  */ /**  * This class is a kind of specialized type-safe Map, from char array to  * String value. Specialization means that in addition to type-safety  * and specific access patterns (key char array, Value optionally interned  * String; values added on access if necessary), and that instances are  * meant to be used concurrently, but by using well-defined mechanisms  * to obtain such concurrently usable instances. Main use for the class  * is to store symbol table information for things like compilers and  * parsers; especially when number of symbols (keywords) is limited.  *<p>  * For optimal performance, usage pattern should be one where matches  * should be very common (esp. after "warm-up"), and as with most hash-based  * maps/sets, that hash codes are uniformly distributed. Also, collisions  * are slightly more expensive than with HashMap or HashSet, since hash codes  * are not used in resolving collisions; that is, equals() comparison is  * done with all symbols in same bucket index.<br />  * Finally, rehashing is also more expensive, as hash codes are not  * stored; rehashing requires all entries' hash codes to be recalculated.  * Reason for not storing hash codes is reduced memory usage, hoping  * for better memory locality.  *<p>  * Usual usage pattern is to create a single "master" instance, and either  * use that instance in sequential fashion, or to create derived "child"  * instances, which after use, are asked to return possible symbol additions  * to master instance. In either case benefit is that symbol table gets  * initialized so that further uses are more efficient, as eventually all  * symbols needed will already be in symbol table. At that point no more  * Symbol String allocations are needed, nor changes to symbol table itself.  *<p>  * Note that while individual SymbolTable instances are NOT thread-safe  * (much like generic collection classes), concurrently used "child"  * instances can be freely used without synchronization. However, using  * master table concurrently with child instances can only be done if  * access to master instance is read-only (ie. no modifications done).  */ public class SymbolTable {     /**      * Default initial table size; no need to make it miniscule, due      * to couple of things: first, overhead of array reallocation      * is significant,      * and second, overhead of rehashing is also non-negligible.      *<p>      * Let's use 128 as the default; it allows for up to 96 symbols,      * and uses about 512 bytes on 32-bit machines.      */     protected static final int DEFAULT_TABLE_SIZE = 128;     protected static final float DEFAULT_FILL_FACTOR = 0.75f;     protected static final String EMPTY_STRING = "";     /*     ////////////////////////////////////////     // Configuration:     ////////////////////////////////////////      */     /**      * Flag that determines whether Strings to be added need to be      * interned before being added or not. Forcing intern()ing will add      * some overhead when adding new Strings, but may be beneficial if such      * Strings are generally used by other parts of system. Note that even      * without interning, all returned String instances are guaranteed      * to be comparable with equality (==) operator; it's just that such      * guarantees are not made for Strings other classes return.      */     protected boolean mInternStrings;     /*     ////////////////////////////////////////     // Actual symbol table data:     ////////////////////////////////////////      */     /**      * Primary matching symbols; it's expected most match occur from      * here.      */     protected String[] mSymbols;     /**      * Overflow buckets; if primary doesn't match, lookup is done      * from here.      *<p>      * Note: Number of buckets is half of number of symbol entries, on      * assumption there's less need for buckets.      */     protected Bucket[] mBuckets;     /**      * Current size (number of entries); needed to know if and when      * rehash.      */     protected int mSize;     /**      * Limit that indicates maximum size this instance can hold before      * it needs to be expanded and rehashed. Calculated using fill      * factor passed in to constructor.      */     protected int mSizeThreshold;     /**      * Mask used to get index from hash values; equal to      * <code>mBuckets.length - 1</code>, when mBuckets.length is      * a power of two.      */     protected int mIndexMask;     /*     ////////////////////////////////////////     // Information about concurrency     ////////////////////////////////////////      */          /**      * Version of this table instance; used when deriving new concurrently      * used versions from existing 'master' instance.      */     protected int mThisVersion;     /**      * Flag that indicates if any changes have been made to the data;      * used to both determine if bucket array needs to be copied when      * (first) change is made, and potentially if updated bucket list      * is to be resync'ed back to master instance.      */     protected boolean mDirty;     /*     ////////////////////////////////////////     // Life-cycle:     ////////////////////////////////////////      */     /**      * Method for constructing a master symbol table instance; this one      * will create master instance with default size, and with interning      * enabled.      */     public SymbolTable() {         this(true);     }     /**      * Method for constructing a master symbol table instance.      */     public SymbolTable(boolean internStrings) {         this(internStrings, DEFAULT_TABLE_SIZE);     }     /**      * Method for constructing a master symbol table instance.      */     public SymbolTable(boolean internStrings, int initialSize) {         this(internStrings, initialSize, DEFAULT_FILL_FACTOR);     }     /**      * Main method for constructing a master symbol table instance; will      * be called by other public constructors.      *      * @param internStrings Whether Strings to add are intern()ed or not      * @param initialSize Minimum initial size for bucket array; internally      *   will always use a power of two equal to or bigger than this value.      * @param fillFactor Maximum fill factor allowed for bucket table;      *   when more entries are added, table will be expanded.      */     public SymbolTable(boolean internStrings, int initialSize,                        float fillFactor)     {         mInternStrings = internStrings;         // Let's start versions from 1         mThisVersion = 1;         // And we'll also set flags so no copying of buckets is needed:         mDirty = true;         // No point in requesting funny initial sizes...         if (initialSize < 1) {             throw new IllegalArgumentException("Can not use negative/zero initial size: "+initialSize);         }         /* Initial size has to be a power of two. Also, let's not honour          * sizes that are ridiculously small...          */         {             int currSize = 4;             while (currSize < initialSize) {                 currSize += currSize;             }             initialSize = currSize;         }         mSymbols = new String[initialSize];         mBuckets = new Bucket[initialSize >> 1];         // Mask is easy to calc for powers of two.         mIndexMask = initialSize - 1;         mSize = 0;         // Sanity check for fill factor:         if (fillFactor < 0.01f) {             throw new IllegalArgumentException("Fill factor can not be lower than 0.01.");         }         if (fillFactor > 10.0f) { // just to catch stupid values, ie. useless from performance perspective             throw new IllegalArgumentException("Fill factor can not be higher than 10.0.");         }         mSizeThreshold = (int) (initialSize * fillFactor + 0.5);     }     /**      * Internal constructor used when creating child instances.      */     private SymbolTable(boolean internStrings, String[] symbols,                         Bucket[] buckets, int size, int sizeThreshold,                         int indexMask, int version)     {         mInternStrings = internStrings;         mSymbols = symbols;         mBuckets = buckets;         mSize = size;         mSizeThreshold = sizeThreshold;         mIndexMask = indexMask;         mThisVersion = version;         // Need to make copies of arrays, if/when adding new entries         mDirty = false;     }     /**      * "Factory" method; will create a new child instance of this symbol      * table. It will be a copy-on-write instance, ie. it will only use      * read-only copy of parent's data, but when changes are needed, a      * copy will be created.      *<p>      * Note: while this method is synchronized, it is generally not      * safe to both use makeChild/mergeChild, AND to use instance      * actively. Instead, a separate 'root' instance should be used      * on which only makeChild/mergeChild are called, but instance itself      * is not used as a symbol table.      */     public synchronized SymbolTable makeChild() {         return new SymbolTable(mInternStrings, mSymbols, mBuckets,                                mSize, mSizeThreshold, mIndexMask,                                mThisVersion+1);     }     /**      * Method that allows contents of child table to potentially be      * "merged in" with contents of this symbol table.      *<p>      * Note that caller has to make sure symbol table passed in is      * really a child or sibling of this symbol table.      */     public synchronized void mergeChild(SymbolTable child)     {         // Let's do a basic sanity check first:         if (child.size() <= size()) { // nothing to add             return;         }         // Okie dokie, let's get the data in!         mSymbols = child.mSymbols;         mBuckets = child.mBuckets;         mSize = child.mSize;         mSizeThreshold = child.mSizeThreshold;         mIndexMask = child.mIndexMask;         mThisVersion++; // to prevent other children from overriding         // Dirty flag... well, let's just clear it, to force copying just         // in case. Shouldn't really matter, for master tables.         mDirty = false;         /* However, we have to mark child as dirty, so that it will not          * be modifying arrays we "took over" (since child may have          * returned an updated table before it stopped fully using          * the SymbolTable: for example, it may still use it for          * parsing PI targets in epilog)          */         child.mDirty = false;     }     /*     ////////////////////////////////////////////////////     // Public API, configuration     ////////////////////////////////////////////////////      */     public void setInternStrings(boolean state) {         mInternStrings = state;     }     /*     ////////////////////////////////////////////////////     // Public API, generic accessors:     ////////////////////////////////////////////////////      */     public int size() { return mSize; }     public int version() { return mThisVersion; }     public boolean isDirty() { return mDirty; }     public boolean isDirectChildOf(SymbolTable t)     {         /* Actually, this doesn't really prove it is a child (would have to          * use sequence number, or identityHash to really prove it), but          * it's good enough if relationship is known to exist.          */         /* (for real check, one would need to child/descendant stuff; or          * at least an identity hash... or maybe even just a _static_ global          * counter for instances... maybe that would actually be worth          * doing?)          */         if (mThisVersion == (t.mThisVersion + 1)) {             return true;         }         return false;     }     /*     ////////////////////////////////////////////////////     // Public API, accessing symbols:     ////////////////////////////////////////////////////      */     /**      * Main access method; will check if actual symbol String exists;      * if so, returns it; if not, will create, add and return it.      *      * @return The symbol matching String in input array      */     /*     public String findSymbol(char[] buffer, int start, int len)     {         return findSymbol(buffer, start, len, calcHash(buffer, start, len));     }     */     public String findSymbol(char[] buffer, int start, int len, int hash)     {         // Sanity check:         if (len < 1) {             return EMPTY_STRING;         }         hash &= mIndexMask;         String sym = mSymbols[hash];         // Optimal case; checking existing primary symbol for hash index:         if (sym != null) {             // Let's inline primary String equality checking:             if (sym.length() == len) {                 int i = 0;                 do {                     if (sym.charAt(i) != buffer[start+i]) {                         break;                     }                 } while (++i < len);                 // Optimal case; primary match found                 if (i == len) {                     return sym;                 }             }             // How about collision bucket?             Bucket b = mBuckets[hash >> 1];             if (b != null) {                 sym = b.find(buffer, start, len);                 if (sym != null) {                     return sym;                 }             }         }         // Need to expand?         if (mSize >= mSizeThreshold) {             rehash();             /* Need to recalc hash; rare occurence (index mask has been              * recalculated as part of rehash)              */             hash = calcHash(buffer, start, len) & mIndexMask;         } else if (!mDirty) {             // Or perhaps we need to do copy-on-write?             copyArrays();             mDirty = true;         }         ++mSize;         String newSymbol = new String(buffer, start, len);         if (mInternStrings) {             newSymbol = newSymbol.intern();         }         // Ok; do we need to add primary entry, or a bucket?         if (mSymbols[hash] == null) {             mSymbols[hash] = newSymbol;         } else {             int bix = hash >> 1;             mBuckets[bix] = new Bucket(newSymbol, mBuckets[bix]);         }         return newSymbol;     }     /**      * Similar to {link #findSymbol}, but will not add passed in symbol      * if it is not in symbol table yet.      */     public String findSymbolIfExists(char[] buffer, int start, int len, int hash)     {         // Sanity check:         if (len < 1) {             return EMPTY_STRING;         }         hash &= mIndexMask;         String sym = mSymbols[hash];         // Optimal case; checking existing primary symbol for hash index:         if (sym != null) {             // Let's inline primary String equality checking:             if (sym.length() == len) {                 int i = 0;                 do {                     if (sym.charAt(i) != buffer[start+i]) {                         break;                     }                 } while (++i < len);                 // Optimal case; primary match found                 if (i == len) {                     return sym;                 }             }             // How about collision bucket?             Bucket b = mBuckets[hash >> 1];             if (b != null) {                 sym = b.find(buffer, start, len);                 if (sym != null) {                     return sym;                 }             }         }         return null;     }     /**      * Similar to to {@link #findSymbol(char[],int,int,int)}; used to either      * do potentially cheap intern() (if table already has intern()ed version),      * or to pre-populate symbol table with known values.      */     public String findSymbol(String str)     {         int len = str.length();         // Sanity check:         if (len < 1) {             return EMPTY_STRING;         }         int index = calcHash(str) & mIndexMask;         String sym = mSymbols[index];         // Optimal case; checking existing primary symbol for hash index:         if (sym != null) {             // Let's inline primary String equality checking:             if (sym.length() == len) {                 int i = 0;                 for (; i < len; ++i) {                     if (sym.charAt(i) != str.charAt(i)) {                         break;                     }                 }                 // Optimal case; primary match found                 if (i == len) {                     return sym;                 }             }             // How about collision bucket?             Bucket b = mBuckets[index >> 1];             if (b != null) {                 sym = b.find(str);                 if (sym != null) {                     return sym;                 }             }         }         // Need to expand?         if (mSize >= mSizeThreshold) {             rehash();             /* Need to recalc hash; rare occurence (index mask has been              * recalculated as part of rehash)              */             index = calcHash(str) & mIndexMask;         } else if (!mDirty) {             // Or perhaps we need to do copy-on-write?             copyArrays();             mDirty = true;         }         ++mSize;         if (mInternStrings) {             str = str.intern();         }         // Ok; do we need to add primary entry, or a bucket?         if (mSymbols[index] == null) {             mSymbols[index] = str;         } else {             int bix = index >> 1;             mBuckets[bix] = new Bucket(str, mBuckets[bix]);         }         return str;     }     /**      * Implementation of a hashing method for variable length      * Strings. Most of the time intention is that this calculation      * is done by caller during parsing, not here; however, sometimes      * it needs to be done for parsed "String" too.      *      * @param len Length of String; has to be at least 1 (caller guarantees      *   this pre-condition)      */     public static int calcHash(char[] buffer, int start, int len) {         int hash = (int) buffer[0];         for (int i = 1; i < len; ++i) {             hash = (hash * 31) + (int) buffer[i];         }         return hash;     }     public static int calcHash(String key) {         int hash = (int) key.charAt(0);         for (int i = 1, len = key.length(); i < len; ++i) {             hash = (hash * 31) + (int) key.charAt(i);         }         return hash;     }     /*     //////////////////////////////////////////////////////////     // Internal methods     //////////////////////////////////////////////////////////      */     /**      * Method called when copy-on-write is needed; generally when first      * change is made to a derived symbol table.      */     private void copyArrays() {         String[] oldSyms = mSymbols;         int size = oldSyms.length;         mSymbols = new String[size];         System.arraycopy(oldSyms, 0, mSymbols, 0, size);         Bucket[] oldBuckets = mBuckets;         size = oldBuckets.length;         mBuckets = new Bucket[size];         System.arraycopy(oldBuckets, 0, mBuckets, 0, size);     }     /**      * Method called when size (number of entries) of symbol table grows      * so big that load factor is exceeded. Since size has to remain      * power of two, arrays will then always be doubled. Main work      * is really redistributing old entries into new String/Bucket      * entries.      */     private void rehash()     {         int size = mSymbols.length;         int newSize = size + size;         String[] oldSyms = mSymbols;         Bucket[] oldBuckets = mBuckets;         mSymbols = new String[newSize];         mBuckets = new Bucket[newSize >> 1];         // Let's update index mask, threshold, now (needed for rehashing)         mIndexMask = newSize - 1;         mSizeThreshold += mSizeThreshold;                  int count = 0; // let's do sanity check         /* Need to do two loops, unfortunately, since spillover area is          * only half the size:          */         for (int i = 0; i < size; ++i) {             String symbol = oldSyms[i];             if (symbol != null) {                 ++count;                 int index = calcHash(symbol) & mIndexMask;                 if (mSymbols[index] == null) {                     mSymbols[index] = symbol;                 } else {                     int bix = index >> 1;                     mBuckets[bix] = new Bucket(symbol, mBuckets[bix]);                 }             }         }         size >>= 1;         for (int i = 0; i < size; ++i) {             Bucket b = oldBuckets[i];             while (b != null) {                 ++count;                 String symbol = b.getSymbol();                 int index = calcHash(symbol) & mIndexMask;                 if (mSymbols[index] == null) {                     mSymbols[index] = symbol;                 } else {                     int bix = index >> 1;                     mBuckets[bix] = new Bucket(symbol, mBuckets[bix]);                 }                 b = b.getNext();             }         }         if (count != mSize) {             throw new IllegalStateException("Internal error on SymbolTable.rehash(): had "+mSize+" entries; now have "+count+".");         }     }     /*     //////////////////////////////////////////////////////////     // Test/debug support:     //////////////////////////////////////////////////////////      */     public double calcAvgSeek() {         int count = 0;         for (int i = 0, len = mSymbols.length; i < len; ++i) {             if (mSymbols[i] != null) {                 ++count;             }         }         for (int i = 0, len = mBuckets.length; i < len; ++i) {             Bucket b = mBuckets[i];             int cost = 2;             while (b != null) {                 count += cost;                 ++cost;                 b = b.getNext();             }         }         return ((double) count) / ((double) mSize);     }     /*     //////////////////////////////////////////////////////////     // Bucket class     //////////////////////////////////////////////////////////      */     /**      * This class is a symbol table entry. Each entry acts as a node      * in a linked list.      */     static final class Bucket {         private final String mSymbol;         private final Bucket mNext;         public Bucket(String symbol, Bucket next) {             mSymbol = symbol;             mNext = next;         }         public String getSymbol() { return mSymbol; }         public Bucket getNext() { return mNext; }         public String find(char[] buf, int start, int len) {             String sym = mSymbol;             Bucket b = mNext;             while (true) { // Inlined equality comparison:                 if (sym.length() == len) {                     int i = 0;                     do {                         if (sym.charAt(i) != buf[start+i]) {                             break;                         }                     } while (++i < len);                     if (i == len) {                         return sym;                     }                 }                 if (b == null) {                     break;                 }                 sym = b.getSymbol();                 b = b.getNext();             }             return null;         }         public String find(String str) {             String sym = mSymbol;             Bucket b = mNext;             while (true) {                 if (sym.equals(str)) {                     return sym;                 }                 if (b == null) {                     break;                 }                 sym = b.getSymbol();                 b = b.getNext();             }             return null;         }     } }