适当地支持不区分大小写的映射

时间:2012-03-21 21:46:57

标签: java

我想实现一个不区分大小写的哈希映射。这个问题本身并不新鲜,但我想增加额外的功能,不知道要采取什么样的方向。我希望客户能够做到这样的事情:

boolean preserve_case = true;
Map<String, MyClass> maplet = new CaseInsensitiveHashMap<MyClass>(preserve_case); // If the client enters true at construction, then the put, get, and remove methods should still be case insensitive, but the entry and key sets should preserve the case that the client used when calling put.

maplet.put("FoO", my_class);

MyClass bar = maplet.get("foo"); // Should return a reference to my_class

Set<Entry<String, MyClass>> case_sensitive_set = maplet.entrySet(); // Since the client input true to preserve order, this entry set should be ["FoO"=my_class.toString()]

我可以很好地处理大部分事情;我只是在后端保留HashMap。当客户端放入任何内容时,我会在将密钥添加到地图之前将其大写。

我在编写keySet()entrySet()方法时遇到了困难。我希望返回的条目集和密钥集由地图支持,这是Java地图的标准。

但是,我能想到处理这个问题的唯一方法是创建第二个支持数据结构,类似于preserved_case_map,其中包含input.toUpperCase() =&gt;输入为键值对。当客户端调用entrySet()(或keySet())时,我可以通过循环preserved_case_map来构造返回的条目集。这里的问题是,如果我对HashMap进行更改,则不会修改返回的条目集,除非我误解了某些内容......

让我知道这是否有意义,或者我是否在卷入一个简单的情况。

6 个答案:

答案 0 :(得分:25)

您可以使用不区分大小写的比较器的TreeMap。 TreeMap将使用比较器以不区分大小写的方式比较键:

    Map<String, Integer> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);

    map.put("Foo", 1);
    map.put("fOo", 2);

    System.out.println(map.get("foo")); // prints 2
    System.out.println(map.keySet()); // prints [Foo]

答案 1 :(得分:5)

为了简化包装地图,您可以使用Googles Guava库中的ForwardingMap,但这是可选的。

在从支持映射中获取/获取内容之前,将String键包装在覆盖hashCode()/ equals()的类中,并使用包装器作为映射中的键。类似的东西:

class KeyWrapper { 
    int hashCode() { 
        return actualStringKey.toUpperCase().hashCode()
    }
    boolean equals(Object o) {...} // compare case-insensitive
}

如果覆盖keySet(),则可以创建一个新集合并使用actualStringKeys填充它。

答案 2 :(得分:4)

您可以使用Apache的Commons Collections中的CaseInsensitiveMap。

http://commons.apache.org/proper/commons-collections/

答案 3 :(得分:3)

不是仅存储值,而是存储输入键(仍然具有用户提供的大小写)和值的KeyValuePair,并使用大写的键作为键。

当你拉出值时,在返回之前从KeyValuePair中提取它。您还可以通过使用大写键执行相同的查找来检索原始键的大小写,然后拉出原始键。

答案 4 :(得分:2)

我在这些行上为我编写的映射缓存做了一些事情。这里更改了命名以反映您的大小写不敏感而不是我的缓存,但概念是相同的。

这会给你一个UncasedMap类,它可以被重用来保存任何东西,但仅限于使用字符串作为键。

// Map that uses case insensitive strings as keys to any kind of element
public class UncasedMap<V> extends HashMap<String, V>
{
    private static final long serialVersionUID = 1L;
    private Map<String, MappedObject> backingMap = new HashMap<String, MappedObject>();

    @Override
    public V put(String key, V value)
    {
        MappedObject currentVal = backingMap.get( key.toUpperCase() );
        backingMap.put( key.toUpperCase(), new MappedObject(key.toUpperCase(), value) );
        return currentVal.getElement();

    }
    @Override
    public V get(Object key)
    {
        return backingMap.get(key.toString().toUpperCase()).getElement();
    }

    // a private inner class of my case insensitive map, to wrap the callers element
    private class MappedObject
    {
        private String key;
        private String orig_key;
        private V mapElement;

        public MappedObject(String keyx, V elem)
        {
            this.orig_key = keyx;
            this.key = keyx.toUpperCase();
            this.mapElement = elem;
        }

        public V getElement()
        {
            return this.mapElement;
        }
    }
}

实施keySet()emptySet()会更复杂,但也会按照相同的方式完成。

答案 5 :(得分:1)

一种方法是从HashMap<String, V>继承,然后在密钥上调用toUpperCase()之后调用基类中的方法。

但是,只需覆盖hashCode()(或您将使用的任何类)中的MyClass方法,然后使用标准HashMap<K, V>类,而无需实现拥有。例如:

public int hashCode() {
    return this.toString().toUperCase().hashCode();
}

如果你把它放在基类中,然后让你的类继承它,那么你将有一个DRY并且非常简单的解决你的问题。