Gostaria de usar uma seqüência sem distinção entre maiúsculas e minúsculas como uma chave HashMap pelos seguintes motivos.
- Durante a inicialização, meu programa cria HashMap com String definida pelo usuário
- Durante o processamento de um evento (tráfego de rede no meu caso), posso receber String em um caso diferente, mas conseguir localizar o
<key, value>
HashMap ignorando o caso que recebi do tráfego.
Eu segui essa abordagem
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()));
Por isso, estou criando um novo objeto de CaseInsensitiveString para cada evento. Portanto, pode afetar o desempenho.
Existe alguma outra maneira de resolver esse problema?
Respostas:
Isso é realmente tudo o que você precisa.
fonte
public static <K extends String, V> Map<K, V> caseInsensitiveMap() { return new TreeMap<K, V>(String.CASE_INSENSITIVE_ORDER); }
<K extends String>
poisString
é final:public static <V> Map<String, V> caseInsensitiveMap() { return new TreeMap<String, V>(String.CASE_INSENSITIVE_ORDER); }
Como sugerido por Guido García em sua resposta aqui :
Ou
https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/map/CaseInsensitiveMap.html
fonte
containsKey()
eremove()
deve ser substituído da mesma maneira queget()
. aHashMap.putAll()
implementação usaput()
, portanto isso não deve ser um problema - desde que a implementação do HashMap permaneça a mesma. ;) também aget()
assinatura do método recebe umObject
argumento as, não aString
. O código também não testa para uma chave nula:super.get(key == null ? null : key.toString().toLowercase());
HashMap(<? extends String, ? extends String> anotherMap)
, não deverá chamar a super implementação do mesmo construtor, pois essa operação não garantirá que suas chaves sejam minúsculas. você poderia usar: emsuper(anotherMap.size()); putAll(anotherMap);
vez disso.CaseInsensitiveMap<String, Integer>
)Uma abordagem é criar uma subclasse personalizada da classe Apache Commons
AbstractHashedMap
, substituindo os métodoshash
eisEqualKeys
para executar hash sem distinção entre maiúsculas e minúsculas e comparação de chaves. (Nota - eu nunca tentei isso sozinho ...)Isso evita a sobrecarga de criação de novos objetos toda vez que você precisa fazer uma pesquisa ou atualização no mapa. E as
Map
operações comuns devem O (1) ... como um regularHashMap
.E se você estiver preparado para aceitar as escolhas de implementação que eles fizeram, o Apache Commons
CaseInsensitiveMap
fará o trabalho de customização / especializaçãoAbstractHashedMap
para você.Mas se O (logN)
get
eput
operações forem aceitáveis, aTreeMap
com um comparador de cadeias sem distinção entre maiúsculas e minúsculas é uma opção; por exemplo, usandoString.CASE_INSENSITIVE_ORDER
.E se você não se importa em criar um novo objeto String temporário toda vez que faz um
put
ouget
, então a resposta de Vishal é ótima. (Porém, note que você não preservaria o caso original das chaves se fizesse isso ...)fonte
Subclasse
HashMap
e crie uma versão que minúscula a chaveput
eget
(e provavelmente os outros métodos orientados a chave).Ou compor a
HashMap
na nova classe e delegar tudo ao mapa, mas traduza as chaves.Se você precisar manter a chave original, poderá manter mapas duplos ou armazenar a chave original junto com o valor.
fonte
HashMap
, então foi isso que eu escolhi :) Ah, você quer dizer o do Commons; Entendo. Eu acho que, contanto que você não precisa dele generified (ou eles finalmente têm genéricos agora?)Duas opções me vêm à mente:
s.toUpperCase().hashCode();
como a chave doMap
.TreeMap<String>
com um costumeComparator
que ignore o caso.Caso contrário, se você preferir sua solução, em vez de definir um novo tipo de String, eu prefiro implementar um novo Mapa com a funcionalidade de insensibilidade de caso necessária.
fonte
Não seria melhor "quebrar" a String para memorizar o hashCode. Na classe String normal, hashCode () é O (N) pela primeira vez e depois é O (1), pois é mantido para uso futuro.
Isso permitiria usar qualquer implementação do Hashtable em java e ter O (1) hasCode ().
fonte
Você pode usar um HashingStrategy baseado
Map
em Eclipse CollectionsNota: Sou colaborador do Eclipse Collections.
fonte
Com base em outras respostas, existem basicamente duas abordagens: subclassificação
HashMap
ou empacotamentoString
. O primeiro requer um pouco mais de trabalho. De fato, se você deseja fazer isso corretamente, você deve substituir quase todos os métodos (containsKey, entrySet, get, put, putAll and remove
).Enfim, tem um problema. Se você deseja evitar problemas futuros, deve especificar uma operação
Locale
noString
caso. Então você criaria novos métodos (get(String, Locale)
, ...). Tudo é mais fácil e mais claro.E bem, sobre suas preocupações com o desempenho: a otimização prematura é a raiz de todos os males :)
fonte
Este é um adaptador para HashMaps que eu implementei para um projeto recente. Funciona de maneira semelhante ao que o @SandyR faz, mas encapsula a lógica de conversão para que você não converta manualmente as strings em um objeto wrapper.
Eu usei os recursos do Java 8, mas com algumas alterações, você pode adaptá-lo às versões anteriores. Testei-o para os cenários mais comuns, exceto as novas funções de fluxo do Java 8.
Basicamente, ele envolve um HashMap, direciona todas as funções para ele enquanto converte seqüências de caracteres para / de um objeto wrapper. Mas eu também tive que adaptar o KeySet e o EntrySet porque eles encaminham algumas funções para o próprio mapa. Então, retorno dois novos conjuntos de chaves e entradas que realmente envolvem o keySet original () e o entrySet ().
Uma observação: o Java 8 mudou a implementação do método putAll, que não consegui encontrar uma maneira fácil de substituir. Portanto, a implementação atual pode ter um desempenho degradado, especialmente se você usar putAll () para um grande conjunto de dados.
Informe-me se você encontrar um bug ou tiver sugestões para melhorar o código.
pacote webbit.collections;
fonte
Criar wrappers ou converter chave em minúscula antes da pesquisa cria novos objetos. Escrever sua própria implementação java.util.Map é a única maneira de evitar isso. Não é muito difícil, e a IMO vale a pena. Eu encontrei a seguinte função hash para funcionar muito bem, até algumas centenas de chaves.
fonte
Que tal usar java 8 streams.
fonte