diff --git a/lib/geocoder.rb b/lib/geocoder.rb
index 22f141050179e738de4b47aed026a864aaaa4091..594f3df8cb35b4e567e92e82f290dfd61d2bcc32 100644
--- a/lib/geocoder.rb
+++ b/lib/geocoder.rb
@@ -1,5 +1,6 @@
 require "geocoder/configuration"
 require "geocoder/calculations"
+require "geocoder/cache"
 require "geocoder/railtie"
 require "geocoder/request"
 
diff --git a/lib/geocoder/cache.rb b/lib/geocoder/cache.rb
new file mode 100644
index 0000000000000000000000000000000000000000..82d47d09113323559c61b5c9ca1a78e9d0862d5e
--- /dev/null
+++ b/lib/geocoder/cache.rb
@@ -0,0 +1,81 @@
+module Geocoder
+
+  ##
+  # The working Cache object, or +nil+ if none configured.
+  #
+  def self.cache
+    if @cache.nil? and store = Geocoder::Configuration.cache
+      @cache = Cache.new(store, Geocoder::Configuration.cache_prefix)
+    end
+    @cache
+  end
+
+  class Cache
+
+    def initialize(store, prefix)
+      @store = store
+      @prefix = prefix
+    end
+
+    ##
+    # Read from the Cache.
+    #
+    def [](url)
+      interpret store[key_for(url)]
+    end
+
+    ##
+    # Write to the Cache.
+    #
+    def []=(url, value)
+      store[key_for(url)] = value
+    end
+
+    ##
+    # Expire cache entry for given URL,
+    # or pass <tt>:all</tt> to expire everything.
+    #
+    def expire(url)
+      if url == :all
+        urls.each{ |u| expire(u) }
+      else
+        self[url] = nil
+      end
+    end
+
+
+    private # ----------------------------------------------------------------
+
+    attr_reader :prefix, :store
+
+    ##
+    # Cache key for a given URL.
+    #
+    def key_for(url)
+      [prefix, url].join
+    end
+
+    ##
+    # Array of keys with the currently configured prefix
+    # that have non-nil values.
+    #
+    def keys
+      store.keys.select{ |k| k.match /^#{prefix}/ and interpret(store[k]) }
+    end
+
+    ##
+    # Array of cached URLs.
+    #
+    def urls
+      keys.map{ |k| k[/^#{prefix}(.*)/, 1] }
+    end
+
+    ##
+    # Clean up value before returning. Namely, convert empty string to nil.
+    # (Some key/value stores return empty string instead of nil.)
+    #
+    def interpret(value)
+      value == "" ? nil : value
+    end
+  end
+end
diff --git a/lib/geocoder/lookups/base.rb b/lib/geocoder/lookups/base.rb
index 65ff21281ef655b5186f5810e4b4a3cc5ff133a0..90c17a3098f2c8dff9430797df6fc86a3bf0f679 100644
--- a/lib/geocoder/lookups/base.rb
+++ b/lib/geocoder/lookups/base.rb
@@ -60,7 +60,7 @@ module Geocoder
         rescue TimeoutError
           warn "Geocoding API not responding fast enough " +
             "(see Geocoder::Configuration.timeout to set limit)."
-        end
+      end
       end
 
       ##
@@ -82,13 +82,12 @@ module Geocoder
       # Fetches a raw search result (JSON string).
       #
       def fetch_raw_data(query, reverse = false)
-        url = query_url(query, reverse)
-        key = cache_key(url)
         timeout(Geocoder::Configuration.timeout) do
-          unless cache and (response = cache[key]) and response != ""
+          url = query_url(query, reverse)
+          unless cache and response = cache[url]
             response = Net::HTTP.get_response(URI.parse(url)).body
             if cache
-              cache[key] = response
+              cache[url] = response
             end
           end
           response
@@ -96,24 +95,10 @@ module Geocoder
       end
 
       ##
-      # Cache key for a given URL.
-      #
-      def cache_key(url)
-        [cache_prefix, url].join
-      end
-
-      ##
-      # The configured prefix for cache keys.
-      #
-      def cache_prefix
-        Geocoder::Configuration.cache_prefix || "geocoder:"
-      end
-
-      ##
-      # The configured cache store.
+      # The working Cache object.
       #
       def cache
-        Geocoder::Configuration.cache
+        Geocoder.cache
       end
 
       ##