Un-hash the Java Hash Hash Un-hashing

The basic goal of this challenge is to exploit the high probability of collision in the Java Hash function to create a list of plausible inputs that created an outputted hash. The program that can print the most results in one hour wins.

For example, hashing golf produces the number 3178594. Other 'words' you could hash to get the same number would be: hQNG, hQMf, hPlf

Details

The basic Java hash function is defined as:

function hash(text){
    h = 0;
    for(i : text) {
        h = 31*h + toCharCode(i);
    }
    return h;
}


You can use an altered or more efficient variant of the hash function (such as using bitwise to replace multiplication) if it produces the same results.

If your language of choice does not use signed ints or have integer overflow, you must implement a wrap function in the hash to emulate integer overflow so that 2147483648 would overflow to -2147483648.

You can choose not to worry about inputs that wrap around multiple times, such as those with 6-7 characters or more. If you choose to include those, you have a better chance of winning.

Additional Requirements


Your code should have at least one input: the hash itself. An optional second input is the maximum number of letters to guess. For example, guessHash(53134) might produce a list of all input combinations up to the absolute maximum amount (see the next requirement), and guessHash(53134, 4) will display all input combinations that contain 1-4 letters.
As there are an infinite amount of results, the most letters that can be in a single result will be set at 256. If your program can manage to output all 256 letter results in a reasonable amount of time (see next requirement), you automatically win.
Your program should not run for more than an hour on any non-specialized computer you use. Non-specialized means it can't run on a server system using 256 different cores or on a super-computer, it must be something that any regular consumer would/could buy. The shorter time period is for practicality, so that you don't have to use all your processing power for too long.
All the results in your final list of inputs must produce the same hash as the inputted hash.
You are to implement your own search, brute-force, or any algorithm to produce the final list. 
Using a native search method or search method from another library is not allowed (example: binary search library). Using String.search(char) or Array.find(value) is okay if it is not a primary feature of your search algorithm (such as to spot duplicate results or find if a letter exists in given string).
The final list of input values should use all readable ASCII characters (32 to 126).


Example

I went ahead and created a very inefficient version of this challenge to produce an example list! Each result I gave is separated by spaces, although you can print each result on a new line or any other readable method of organization.

i30( i3/G i2NG hQNG hQMf hPlf golf


You do not have to print results to a file, just record how many results you get.

Judging

The number to un-hash is: 69066349

I will pick the best answer based on which-ever code produces the most results.  If you remember above where I said wrapping was optional, it'd be a good idea to include multiple wrappings if you want the most results in your final list.



If you have any questions or additional comments, feel free to ask.

Good Luck!
C#, 1.052 × 1010

The idea is to do the hash function in reverse to get all possible un-hashed strings from the given number. Integer overflow of the 32-bit hash value is also emulated in reverse by incrementing the given hash value by 2^32 on each iteration.

UPDATE: I've made several micro-optimisations that significantly improved the performance of my program. The new version produced 10.52 billion results in 15 minutes (compared to 9.05 billion in 1 hour for the previous version).

Futher improvement may be difficult since the output file was 117GB in size and that's about how much free space I have on my SSD. Writing results to the screen doesn't seem to be a viable option since it's almost a thousand times slower than writing to a file.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;

namespace Unhash
{
    class Program
    {
        const int minChar = 32;

        const int maxChar = 126;

        // limit maxStrLength to make sure that max hash value does not exceed long.MaxValue (2^63 - 1)
        const int maxStrLength = 12;

        static long[] minHashValues;

        static long[] maxHashValues;

        static Program()
        {
            minHashValues = new long[maxStrLength + 1];
            for (int strLength = 0; strLength &lt;= maxStrLength; strLength++)
                minHashValues[strLength] = Pow(31, strLength) / (31 - 1) * minChar;

            maxHashValues = new long[maxStrLength + 1];
            for (int strLength = 0; strLength &lt;= maxStrLength; strLength++)
                maxHashValues[strLength] = Pow(31, strLength) / (31 - 1) * maxChar;
        }

        static void GuessHash(int hash, int maxLength = 256)
        {
            Stopwatch sw = Stopwatch.StartNew();
            var timeLimit = new TimeSpan(0, 15, 0);

            if (maxLength &gt; maxStrLength)
                maxLength = maxStrLength;

            long currentHash = (uint)hash;
            long step = 1L &lt;&lt; 32;

            const int bufferSize = 32 * 1024;
            using (var writer = new StreamWriter("output.txt", false, new UTF8Encoding(false), bufferSize))
            {
                for (int strLength = 0; strLength &lt;= maxLength; strLength++)
                {
                    long maxHash = maxHashValues[strLength];
                    if (currentHash &gt; maxHash)
                        continue;

                    long minHash = minHashValues[strLength];
                    while (currentHash &lt; minHash)
                        currentHash += step;

                    while ((currentHash &lt;= maxHash) &amp;&amp; (sw.Elapsed &lt; timeLimit))
                    {
                        GuessLongHash(writer, currentHash, new char[strLength], strLength - 1);
                        currentHash += step;
                    }
                }
            }
        }

        static void GuessLongHash(StreamWriter writer, long hash, char[] chars, int charPos)
        {
            if (hash &lt;= maxChar)
            {
                char ch = (char)hash;
                if (ch &gt;= minChar)
                {
                    chars[charPos] = ch;
                    writer.WriteLine(new string(chars));
                }
                return;
            }

            char c = (char)(hash % 31);
            while (c &lt; minChar)
                c += (char)31;

            while (c &lt;= maxChar)
            {
                chars[charPos] = c;
                GuessLongHash(writer, (hash - c) / 31, chars, charPos - 1);

                c += (char)31;
            }
        }

        static long Pow(int value, int exponent)
        {
            long result = 1;
            for (int i = 0; i &lt; exponent; i++)
                result *= value;
            return result;
        }

        static void Main(string[] args)
        {
            const int hash = 69066349;
            GuessHash(hash);
        }
    }
}