出于以下原因,我想使用不区分大小写的字符串作为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的新对象。因此,它可能会影响性能。

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


当前回答

而不是创建自己的类来验证和存储大小写不敏感的字符串作为HashMap键,你可以使用:

LinkedCaseInsensitiveMap包装了一个LinkedHashMap,它是一个基于哈希表和链表的Map。与LinkedHashMap不同,它不允许插入空键。LinkedCaseInsensitiveMap保留了键的原始顺序和原始大小写,同时允许使用任何大小写调用get和remove等函数。

Eg:

Map<String, Integer> linkedHashMap = new LinkedCaseInsensitiveMap<>();
linkedHashMap.put("abc", 1);
linkedHashMap.put("AbC", 2);

System.out.println(linkedHashMap);

输出:AbC = {2}

Mvn依赖性:

Spring Core是一个Spring框架模块,它还提供实用工具类,包括LinkedCaseInsensitiveMap。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>

CaseInsensitiveMap是一个基于哈希的Map,它在添加或检索键之前将键转换为小写。与TreeMap不同,CaseInsensitiveMap允许插入空键。

Eg:

Map<String, Integer> commonsHashMap = new CaseInsensitiveMap<>();
commonsHashMap.put("ABC", 1);
commonsHashMap.put("abc", 2);
commonsHashMap.put("aBc", 3);

System.out.println(commonsHashMap);

输出:abc = {3}

依赖:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.4</version>
</dependency>

TreeMap是NavigableMap的实现,这意味着它总是在插入条目后根据给定的Comparator对条目进行排序。此外,TreeMap使用Comparator来查找插入的键是重复的还是新的。

因此,如果我们提供一个不区分大小写的String Comparator,我们将得到一个不区分大小写的TreeMap。

Eg:

Map<String, Integer> treeMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
treeMap.put("ABC", 1);
treeMap.put("ABc", 2);
treeMap.put("cde", 1);
        
System.out.println(treeMap);

输出:{ABC=2, cde=1}

其他回答

正如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

继承HashMap的子类,并创建一个在put和get(可能还有其他面向键的方法)时小写键的版本。

或者将HashMap合成到新类中,并将所有内容委托给映射,但要转换键。

如果需要保留原始键,可以维护双映射,或者将原始键与值一起存储。

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

创建包装器或在查找前将键转换为小写都会创建新对象。编写自己的java.util.Map实现是避免这种情况的唯一方法。这并不难,而且在我看来是值得的。我发现下面的哈希函数工作得很好,最多几百个键。

static int ciHashCode(String string)
{
    // length and the low 5 bits of hashCode() are case insensitive
    return (string.hashCode() & 0x1f)*33 + string.length();
}

而不是创建自己的类来验证和存储大小写不敏感的字符串作为HashMap键,你可以使用:

LinkedCaseInsensitiveMap包装了一个LinkedHashMap,它是一个基于哈希表和链表的Map。与LinkedHashMap不同,它不允许插入空键。LinkedCaseInsensitiveMap保留了键的原始顺序和原始大小写,同时允许使用任何大小写调用get和remove等函数。

Eg:

Map<String, Integer> linkedHashMap = new LinkedCaseInsensitiveMap<>();
linkedHashMap.put("abc", 1);
linkedHashMap.put("AbC", 2);

System.out.println(linkedHashMap);

输出:AbC = {2}

Mvn依赖性:

Spring Core是一个Spring框架模块,它还提供实用工具类,包括LinkedCaseInsensitiveMap。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>

CaseInsensitiveMap是一个基于哈希的Map,它在添加或检索键之前将键转换为小写。与TreeMap不同,CaseInsensitiveMap允许插入空键。

Eg:

Map<String, Integer> commonsHashMap = new CaseInsensitiveMap<>();
commonsHashMap.put("ABC", 1);
commonsHashMap.put("abc", 2);
commonsHashMap.put("aBc", 3);

System.out.println(commonsHashMap);

输出:abc = {3}

依赖:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.4</version>
</dependency>

TreeMap是NavigableMap的实现,这意味着它总是在插入条目后根据给定的Comparator对条目进行排序。此外,TreeMap使用Comparator来查找插入的键是重复的还是新的。

因此,如果我们提供一个不区分大小写的String Comparator,我们将得到一个不区分大小写的TreeMap。

Eg:

Map<String, Integer> treeMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
treeMap.put("ABC", 1);
treeMap.put("ABc", 2);
treeMap.put("cde", 1);
        
System.out.println(treeMap);

输出:{ABC=2, cde=1}

为了记住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()。