diff --git a/lucene/src/java/org/apache/lucene/util/automaton/fst/NodeHash.java b/lucene/src/java/org/apache/lucene/util/automaton/fst/NodeHash.java
index 02719d8..f65ba33 100644
--- a/lucene/src/java/org/apache/lucene/util/automaton/fst/NodeHash.java
+++ b/lucene/src/java/org/apache/lucene/util/automaton/fst/NodeHash.java
@@ -22,14 +22,26 @@ import java.io.IOException;
 // Used to dedup states (lookup already-frozen states)
 final class NodeHash<T> {
 
+  private static final float LOAD_FACTOR = 0.5f;
+  private static final int DEFAULT_CAPACITY = 16; 
+
   private int[] table;
+  
+  /** Maximum number of elements in {@link #table} before rehashing. */
+  private int capacity;
+  
+  /** Number of assigned elements in {@link #table}. */
   private int count;
+  
+  /** Bitmask equal table.length - 1 */
   private int mask;
+
   private final FST<T> fst;
   private final FST.Arc<T> scratchArc = new FST.Arc<T>();
 
   public NodeHash(FST<T> fst) {
-    table = new int[16];
+    table = new int[DEFAULT_CAPACITY];
+    capacity = (int) (table.length * LOAD_FACTOR);
     mask = 15;
     this.fst = fst;
   }
@@ -81,12 +93,13 @@ final class NodeHash<T> {
       }
     }
     //System.out.println("  ret " + (h&Integer.MAX_VALUE));
-    return h & Integer.MAX_VALUE;
+    return mix(h);
   }
 
   // hash code for a frozen node
   private int hash(int node) throws IOException {
     final int PRIME = 31;
+    final FST.Arc<T> scratchArc = this.scratchArc;
     //System.out.println("hash frozen");
     int h = 0;
     fst.readFirstRealArc(node, scratchArc);
@@ -105,60 +118,62 @@ final class NodeHash<T> {
       fst.readNextRealArc(scratchArc);
     }
     //System.out.println("  ret " + (h&Integer.MAX_VALUE));
-    return h & Integer.MAX_VALUE;
+    return mix(h);
+  }
+
+  /**
+   * An additional bit-avalanching step -- finalization from MurmurHash3. This is used
+   * to ensure proper element distribution even though we use linear probing. 
+   */
+  private int mix(int k) {
+    k ^= k >>> 16;
+    k *= 0x85ebca6b;
+    k ^= k >>> 13;
+    k *= 0xc2b2ae35;
+    k ^= k >>> 16;
+    return k;
   }
 
   public int add(Builder.UnCompiledNode<T> node) throws IOException {
-    // System.out.println("hash: add count=" + count + " vs " + table.length);
-    final int h = hash(node);
-    int pos = h & mask;
-    int c = 0;
-    while(true) {
-      final int v = table[pos];
-      if (v == 0) {
-        // freeze & add
-        final int address = fst.addNode(node);
-        //System.out.println("  now freeze addr=" + address);
-        assert hash(address) == h : "frozenHash=" + hash(address) + " vs h=" + h;
-        count++;
-        table[pos] = address;
-        if (table.length < 2*count) {
-          rehash();
-        }
-        return address;
-      } else if (nodesEqual(node, v)) {
-        // same node is already here
+    if (count >= capacity)
+      rehash();
+
+    // Linear probing.
+    final int mask = this.mask;
+    int v, pos = hash(node) & mask;
+    while ((v = table[pos]) != 0) {
+      if (nodesEqual(node, v)) {
         return v;
       }
-
-      // quadratic probe
-      pos = (pos + (++c)) & mask;
+      pos = (pos + 1) & mask;
     }
-  }
 
-  // called only by rehash
-  private void addNew(int address) throws IOException {
-    int pos = hash(address) & mask;
-    int c = 0;
-    while(true) {
-      if (table[pos] == 0) {
-        table[pos] = address;
-        break;
-      }
-
-      // quadratic probe
-      pos = (pos + (++c)) & mask;
-    }
+    // freeze & add
+    count++;
+    final int address = fst.addNode(node);
+    assert hash(address) == hash(node) : "frozenHash=" + hash(address) + " vs h=" + hash(node);
+    table[pos] = address;
+    return address;
   }
 
   private void rehash() throws IOException {
     final int[] oldTable = table;
-    table = new int[2*table.length];
-    mask = table.length-1;
-    for(int idx=0;idx<oldTable.length;idx++) {
+    final int newSize = oldTable.length << 1;
+    if (newSize < 0)
+      throw new IOException("NodeHash table exceeded maximum positive int size.");
+
+    this.capacity = (int) (newSize * LOAD_FACTOR);
+    final int [] table = this.table = new int [newSize];
+    final int mask = this.mask = newSize - 1;
+    for (int idx = 0; idx < oldTable.length; idx++) {
       final int address = oldTable[idx];
       if (address != 0) {
-        addNew(address);
+        // Linear probing. There must be an empty slot.
+        int pos = hash(address) & mask;
+        while (table[pos] != 0) {
+          pos = (pos + 1) & mask;
+        }
+        table[pos] = address;
       }
     }
   }
