地图中的查找子字符串,不区分大小写

时间:2020-06-22 15:00:10

标签: java string hashmap

我写了一个小词法分析器,将字符缓冲区转换成令牌流。令牌的一种类型是标识符,可以是“原始”或关键字。为了测试后者,我有一张包含所有关键字的地图。

Map<String, MyType> lookup = new HashMap<>();
lookup.put("RETURN", KEYWORD_RETURN);
[...]

该地图填充了所有大写字符串。

现在,我从输入字符缓冲区中得到的只是一个偏移量和一个长度,可以在其中找到我的标识符(不必在这里大写)。

显而易见的解决方案看起来像这样。

bool lookupIdentifier(CharBuffer buffer, int offset, int length, Map<String, MyType> lookupTable) {
    int current = buffer.position();
    buffer.rewind();
    String toCheck = buffer.subSequence(offset, offset + length).toString().toUpperCase();
    buffer.position(current);
    return lookupTable.containsKey(toCheck);
}

地图上大约有50个条目。带有不区分大小写的比较器的TreeMap是O(1)HashMap查找的一个很好的选择吗?

我对我的方法不满意的是toCheck字符串的创建是分配的。有没有一种方法可以重用CharBuffer中的子字符串进行查找?

1 个答案:

答案 0 :(得分:5)

通过使用CharBuffer作为键类型,可以避免昂贵的字符串构造:

Map<CharBuffer, MyType> lookup = new TreeMap<>(Comparator
    .comparingInt(CharBuffer::remaining)
    .thenComparing((cb1,cb2) -> {
        for(int p1 = cb1.position(), p2 = cb2.position(); p1 < cb1.limit(); p1++, p2++) {
            char c1 = cb1.get(p1), c2 = cb2.get(p2);
            if(c1 == c2) continue;
            c1 = Character.toUpperCase(c1);
            c2 = Character.toUpperCase(c2);
            if(c1 != c2) return Integer.compare(c1, c2);
        }
        return 0;
    }));
lookup.put(CharBuffer.wrap("RETURN"), MyType.KEYWORD_RETURN);
boolean lookupIdentifier(
    CharBuffer buffer, int offset, int length, Map<CharBuffer, MyType> lookupTable) {

    int currentPos = buffer.position(), currLimit = buffer.limit();
    buffer.clear().position(offset).limit(offset + length);
    boolean result = lookupTable.containsKey(buffer);
    buffer.clear().position(currentPos).limit(currLimit);
    return result;
}

比较器在执行不区分大小写的字符比较之前使用便宜的长度比较。假设您使用RETURN之类的关键字,它们具有简单的大小写映射。

对于具有50个关键字的地图,使用log²比较进行查找可能仍会产生合理的性能。请注意,每次比较都会在第一个不匹配处停止。


您可以将哈希与专用包装对象一起使用:

final class LookupKey {
    final CharBuffer cb;
    LookupKey(CharBuffer cb) {
        this.cb = cb;
    }
    @Override public int hashCode() {
        int code = 1;
        for(int p = cb.position(); p < cb.limit(); p++) {
            code = Character.toUpperCase(cb.get(p)) + code * 31;
        }
        return code;
    }
    @Override public boolean equals(Object obj) {
        if(!(obj instanceof LookupKey)) return false;
        final LookupKey other = (LookupKey)obj;
        CharBuffer cb1 = this.cb, cb2 = other.cb;
        if(cb1.remaining() != cb2.remaining()) return false;
        for(int p1 = cb1.position(), p2 = cb2.position(); p1 < cb1.limit(); p1++, p2++) {
            char c1 = cb1.get(p1), c2 = cb2.get(p2);
            if(c1 == c2) continue;
            c1 = Character.toUpperCase(c1);
            c2 = Character.toUpperCase(c2);
            if(c1 != c2) return false;
        }
        return true;
    }
}
Map<LookupKey, MyType> lookup = new HashMap<>();
lookup.put(new LookupKey(CharBuffer.wrap("RETURN")), MyType.KEYWORD_RETURN);
boolean lookupIdentifier(
    CharBuffer buffer, int offset, int length, Map<LookupKey, MyType> lookupTable) {

    int currentPos = buffer.position(), currLimit = buffer.limit();
    buffer.clear().position(offset).limit(offset + length);
    boolean result = lookupTable.containsKey(new LookupKey(buffer));
    buffer.clear().position(currentPos).limit(currLimit);
    return result;
}

LookupKey这样的轻量级对象的构造与String不同,它不需要复制字符内容,因此可以忽略不计。但是请注意,与比较器不同,散列必须预先处理所有字符,这可能比小型TreeMap的log2比较要昂贵。

如果这些关键字不太可能更改,则在关键字字符串的不变属性上使用显式查找代码(即switch)可能会更加有效。例如。首先切换length(如果大多数关键字的长度不同),然后切换一个对于大多数关键字都不同的字符(包括大写和小写变体的case标签)。另一种选择是对这些属性进行分层查找。

相关问题