在Java中,创建一个通用的解析字符串缓存(多个结果类型)

时间:2011-10-04 21:29:55

标签: java

我正在尝试创建一个通用的解析字符串缓存,以防止反复重建同一个对象。基本上,我正在构建的内容如下:

public class ParsedStringCache {

    // static Map<Pair<String, Parser<T>>, T> _cache
    //      = new HashMap<Pair<String, Parser<T>>, T>();

    public interface StringParser<T> {
        public T parseString(String stringToParse);
    }

    public static <T> T getParsedObject(String stringToParse, Parser<T> parser) {
        return parser.parseString(stringToParse);
    }
}

这很好用,直到我尝试实际缓存结果,例如在上面的代码中使用类似于注释掉的hashmap的东西,这实际上会记住getParsedObject的结果。是否有一种相当简单的方法来避免投射?

3 个答案:

答案 0 :(得分:3)

我会将缓存作为解析器的一部分。我确定你会遇到一些你需要解决的编译错误,但这是我要做的基础知识。

import java.util.*;
abstract class Parser<T> {

    private Map<String,T> cache = new HashMap<String,T>();

    public final T parseString(String str) {
        T result = cache.get(str);
        if(result == null) {
            result = parseString0(str);
            cache.put(str,result);
        }
        return result;
    }

    protected abstract T parseString0(String str);

}

public class IntParser extends Parser<Integer> {

    protected Integer parseString0(String str) {
        return Integer.parseInt(str.trim());
    }

}

public class LongParser extends Parser<Long> {

    protected Long parseString0(String str) {
        return Long.parseLong(str.trim());
    }

}

class ParserTest {
    public static void main(String[] args) {
        Parser<Integer> intParse = new IntParser();
        Parser<Long> longParse = new LongParser();

        Long long1 = longParse.parseString("10000");
        Long long2 = longParse.parseString("20000");
        Long long3 = longParse.parseString("30000");
        Long equalLong = longParse.parseString("20000"); // repeat long2
        Long fakeLong = new LongParser().parseString("20000"); // repeated with fake
        System.out.println("Expecting true: " + (long2 == equalLong));
        System.out.println("Expecting false: " + (fakeLong == equalLong));
    }
}

C:\Documents and Settings\glowcoder\My Documents>javac Parser.java

C:\Documents and Settings\glowcoder\My Documents>javac IntParser.java

C:\Documents and Settings\glowcoder\My Documents>javac LongParser.java

C:\Documents and Settings\glowcoder\My Documents>javac ParserTest.java

C:\Documents and Settings\glowcoder\My Documents>java ParserTest
Expecting true: true
Expecting false: false

我会保持他们的管理方式,但添加方法来收集信息并向中央经理报告。显然,您需要向解析器添加更多方法以跟踪缓存。或者(这可能是一个更好的主意)您可以将缓存逻辑合并到Cache类中,让管理器跟踪这些缓存。

通过这种方式,您可以让缓存本身确定如何修剪其信息。它可以是最古老的,也可以是大小,它可以是各种标准。也许它有用户信息,您希望付费用户更多地留在缓存中。

import java.util.*;
class ParserCacheManager {

    private Set<Parser<?>> parsers = new HashSet<Parser<?>>();

    public void addParser(Parser<?> p) { parsers.add(p); }

    public int size() {
        int size = 0;
        for(Parser<?> p : parsers) size += p.cacheSize();
        return size;
    }

    public void trimToSize(int maxSize) {
        while(size() > maxSize) {
            Parser<?> p = largestParser();
            p.trimToPercent(0.90);
        }
    }

    /**
     * This would be better perhaps with a set sorted by
     * size, or something like that. But this works for example.
     */
    private Parser<?> largestParser() {
        Parser<?> largest = null;
        for(Parser<?> p : parsers) {
            if(largest == null || p.size() > largest.size())
                largest = p;
        }
        return largest;
    }

}

答案 1 :(得分:1)

除非您将此作为练习,否则请考虑使用Guava的CacheBuilder(或更通用的MapMaker)来执行此任务。它将处理所有并发和过期,并且可以轻松,流畅地配置:

Cache<String, T> parsedObjectCache = CacheBuilder.newBuilder()
   .expireAfterAccess(10, TimeUnit.MINUTES)
   .build(
       new CacheLoader<String, T>() {
         public T load(String str) throws MyParseException {
           return parse(str); //called either on or from inside a Parser<T>
         }
       });

如果你想要一个集中式缓存,你需要编写一个自定义密钥类,它包含对StringParser<?>的引用,并覆盖hashcode / equals。

但是,您可能最好为每个解析器实现单独的Cache<String, T>,因为尝试将所有内容存储在同一Collection中时,类型信息将会丢失。

妥协方法是保留一个主Map<Parser<?>, Cache<String, ?>>,您可以使用Cache根据Parser查找每个Class<?>(或者使用T Cache<String, T> 1}}作为关键)。但是,你仍然会以这种方式丢失通用类型信息,并且必须进行投射。

您可以考虑@glowcoder's suggestion并将Parser<T>整合到每个{{1}}中。这似乎是一种在不进行转换的情况下维护泛型类型信息的合理方法。

答案 2 :(得分:0)

你必须使ParserStringCache成为通用的,你不能在静态类变量上使用任意泛型参数,它必须是非静态成员。

public class ParsedStringCache< T > {

    Map<Pair<String, Parser<T>>, T> _cache =
      new HashMap<Pair<String, Parser<T>>, T>();
}