出于以下原因,我想使用不区分大小写的字符串作为HashMap键。

在初始化过程中,我的程序用用户定义的字符串创建HashMap 在处理事件(在我的情况下是网络流量)时,我可能会在不同的情况下收到字符串,但我应该能够定位<键,值>从HashMap忽略我从流量收到的情况。

我采用了这种方法

CaseInsensitiveString.java

    public final class CaseInsensitiveString {
            private String s;

            public CaseInsensitiveString(String s) {
                            if (s == null)
                            throw new NullPointerException();
                            this.s = s;
            }

            public boolean equals(Object o) {
                            return o instanceof CaseInsensitiveString &&
                            ((CaseInsensitiveString)o).s.equalsIgnoreCase(s);
            }

            private volatile int hashCode = 0;

            public int hashCode() {
                            if (hashCode == 0)
                            hashCode = s.toUpperCase().hashCode();

                            return hashCode;
            }

            public String toString() {
                            return s;
            }
    }

LookupCode.java

    node = nodeMap.get(new CaseInsensitiveString(stringFromEvent.toString()));

因此,我为每个事件创建了CaseInsensitiveString的新对象。因此,它可能会影响性能。

有没有其他办法解决这个问题?


当前回答

为了记住hashCode,“包装”字符串不是更好吗?在普通的String类中,hashCode()第一次是O(N),然后是O(1),因为它是为将来使用而保留的。

public class HashWrap {
    private final String value;
    private final int hash;

    public String get() {
        return value;
    }

    public HashWrap(String value) {
        this.value = value;
        String lc = value.toLowerCase();
        this.hash = lc.hashCode();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o instanceof HashWrap) {
            HashWrap that = (HashWrap) o;
            return value.equalsIgnoreCase(that.value);
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        return this.hash;
    }

    //might want to implement compare too if you want to use with SortedMaps/Sets.
}

这将允许您在java中使用哈希表的任何实现,并具有O(1) hasCode()。

其他回答

您可以使用来自Eclipse Collections的基于HashingStrategy的映射

HashingStrategy<String> hashingStrategy =
    HashingStrategies.fromFunction(String::toUpperCase);
MutableMap<String, String> node = HashingStrategyMaps.mutable.of(hashingStrategy);

注意:我是Eclipse Collections的贡献者。

正如Guido García在他们的回答中所建议的:

import java.util.HashMap;

public class CaseInsensitiveMap extends HashMap<String, String> {

    @Override
    public String put(String key, String value) {
       return super.put(key.toLowerCase(), value);
    }

    // not @Override because that would require the key parameter to be of type Object
    public String get(String key) {
       return super.get(key.toLowerCase());
    }
}

Or

https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/map/CaseInsensitiveMap.html

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

这就是你所需要的。

一种方法是创建Apache Commons AbstractHashedMap类的自定义子类,覆盖散列和isEqualKeys方法来执行不区分大小写的散列和键的比较。(注:我自己从来没有试过……)

这避免了每次需要执行映射查找或更新时创建新对象的开销。常见的Map操作应该是O(1)…就像一个普通的HashMap。

如果您准备接受他们所做的实现选择,Apache Commons CaseInsensitiveMap将为您定制/专门化AbstractHashedMap。


但是如果O(logN)获取和放置操作是可接受的,那么带有不区分大小写字符串比较器的TreeMap是一个选项;例如,使用String.CASE_INSENSITIVE_ORDER。

如果您不介意每次执行put或get操作时创建一个新的临时String对象,那么Vishal的答案就很好。(不过,我注意到,如果你这样做,你就不会保留键的原始大小写…)

我发现需要你改变键的解决方案(例如,toLowerCase)非常不受欢迎,需要TreeMap的解决方案也不受欢迎。

由于TreeMap改变了时间复杂度(与其他hashmap相比),我认为简单地使用O(n)的实用方法更可行:

public static <T> T getIgnoreCase(Map<String, T> map, String key) {
    for(Entry<String, T> entry : map.entrySet()) {
        if(entry.getKey().equalsIgnoreCase(key))
            return entry.getValue();
    }
    return null;
}

这就是那个方法。由于牺牲性能(时间复杂度)看起来是不可避免的,至少不需要更改底层映射以适应查找。