不区分大小写的字符串作为HashMap密钥

问题描述 投票:153回答:12

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

  • 初始化期间,我的程序使用用户定义的字符串创建HashMap
  • [处理事件(在我的情况下为网络流量)时,我可能会在其他情况下收到String,但我应该能够忽略哈希从流量中收到的情况而从HashMap中找到<key, value>

我遵循了这种方法

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新对象。因此,它可能会影响性能。

还有其他解决方法吗?

java dictionary case-insensitive
12个回答
276
投票
Map<String, String> nodeMap = 
    new TreeMap<>(String.CASE_INSENSITIVE_ORDER);

这实际上就是您所需要的。


0
投票

这是我为最近的项目实现的HashMaps适配器。其工作方式与@SandyR相似,但是封装了转换逻辑,因此您不必手动将字符串转换为包装对象。

我使用了Java 8功能,但进行了一些更改,您可以使其适应以前的版本。除了新的Java 8流功能之外,我已经针对大多数常见场景进行了测试。

基本上,它包装HashMap,在将字符串转换到包装对象或从包装对象转换字符串时,将所有函数定向到它。但是我还必须改编KeySet和EntrySet,因为它们会将一些函数转发到地图本身。因此,我为键和条目返回了两个新的Set,它们实际上包装了原始的keySet()和entrySet()。

注意:Java 8改变了putAll方法的实现,我找不到简单的方法来覆盖它。因此,当前的实现可能会降低性能,尤其是如果您对大型数据集使用putAll()。

[如果您发现错误或有改进代码的建议,请告诉我。

包webbit.collections;

get(String, Locale)

0
投票

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

在查找之前创建包装器或将键转换为小写字母都会创建新对象。编写自己的java.util.Map实现是避免这种情况的唯一方法。这并不难,海事组织值得。我发现以下哈希函数可以很好地工作,最多可以使用数百个键。

public final class CaseInsensitiveString {

    private final String s;

    public CaseInsensitiveString(String s, Locale locale) {
        this.s = s.toUpperCase(locale);
    }

    // equals, hashCode & toString, no need for memoizing hashCode
}

-2
投票

如何使用Java 8流。

https://github.com/jdereg/java-util

56
投票

如GuidoGarcía在their answer here中所建议:

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());
    }
}

http://commons.apache.org/proper/commons-collections/javadocs/api-release/org/apache/commons/collections4/map/CaseInsensitiveMap.html


14
投票

一种方法是创建Apache Commons AbstractHashedMap类的自定义子类,重写AbstractHashedMaphash方法以执行不区分大小写的哈希和键比较。 (注意-我从未尝试过此方法...)

这避免了每次需要进行地图查找或更新时创建新对象的开销。和普通的isEqualKeys操作应为O(1)...就像常规的Map

并且,如果您准备接受他们所做的实现选择,则Apache Commons HashMap会为您定制​​/专门化CaseInsensitiveMap


但是如果可以接受O(logN)CaseInsensitiveMapAbstractHashedMap操作,则可以选择不区分大小写的字符串比较器的get;例如使用put

并且如果您不介意每次执行TreeMapString.CASE_INSENSITIVE_ORDER时都创建一个新的临时String对象,那么Vishal的回答就很好。 (尽管如此,如果您这样做,您将不会保留键的原始大小写...)


6
投票

子类String.CASE_INSENSITIVE_ORDER,并创建一个将putget上的键小写的版本(可能还有其他面向键的方法)。

或将HashMap组合到新类中,并将所有内容委托给地图,但转换键。

如果需要保留原始密钥,则可以维护双重映射,也可以将原始密钥与值一起存储。


4
投票

我想到两个选择:

  1. 您可以直接将put用作get的键。
  2. 您可以将HashMap与自定义s.toUpperCase().hashCode();一起使用,而忽略大小写。

否则,如果您更喜欢解决方案,而不是定义一种新的String,我宁愿使用所需的区分大小写功能实现一个新的Map。


3
投票

为了记住hashCode,“包装” String会更好。在普通的String类中,hashCode()第一次是O(N),然后是O(1),因为它被保留以备将来使用。

Map

这将允许您使用Java中Hashtable的任何实现,并具有O(1)hasCode()。


3
投票

您可以使用TreeMap<String>中基于Comparatorpublic 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. }

HashingStrategy

注意:我是Eclipse Collections的撰稿人。


1
投票

基于其他答案,基本上有两种方法:子类化Map或包装Eclipse Collections。第一个需要更多的工作。实际上,如果要正确执行此操作,则必须重写几乎所有方法(HashingStrategy<String> hashingStrategy = HashingStrategies.fromFunction(String::toUpperCase); MutableMap<String, String> node = HashingStrategyMaps.mutable.of(hashingStrategy); )。

反正有问题。如果要避免将来出现问题,则必须在HashMap大小写操作中指定String。因此,您将创建新的方法(containsKey, entrySet, get, put, putAll and remove,...)。一切都更加容易和清晰地包装字符串:

Locale

而且,关于您对性能的担心:过早的优化是万恶之源:)


1
投票

有关健壮的CaseInsensitiveMap / CaseInsensitiveSet实现,请查看java-util(String)。

这些地图在标准O(1)查找时间内执行,保留添加项的大小写,支持所有Map API,例如putAll(),retainAll(),removeAll(),并允许将异构项放入密钥集中。

此外,.keySet()和.entrySet()返回的java.util.Set区分大小写(许多实现不区分大小写)。最后,如果在迭代时从键/条目集中获取键,则会返回一个String,而不是CaseInsensitiveString包装器类。

© www.soinside.com 2019 - 2024. All rights reserved.