Je voudrais utiliser la chaîne insensible à la casse comme clé HashMap pour les raisons suivantes.
<key, value>
de HashMap en ignorant le cas que j'ai reçu du trafic.J'ai suivi cette approche
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()));
Pour cette raison, je crée un nouvel objet CaseInsensitiveString pour chaque événement. Donc, cela pourrait affecter les performances.
Existe-t-il un autre moyen de résoudre ce problème?
Map<String, String> nodeMap =
new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
C'est vraiment tout ce dont vous avez besoin.
Comme suggéré par Guido García dans leur réponse ici :
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());
}
}
Ou
Une approche consiste à créer une sous-classe personnalisée de la classe Apache Commons AbstractHashedMap
en remplaçant les méthodes hash
et isEqualKeys
pour effectuer un hachage et une comparaison des clés insensibles à la casse. (Remarque - je n'ai jamais essayé cela moi-même ...)
Cela évite la surcharge liée à la création de nouveaux objets chaque fois que vous devez effectuer une recherche ou une mise à jour de carte. Et les opérations Map
communes doivent O(1) ... comme un HashMap
normal.
Et si vous êtes prêt à accepter les choix d’implémentation qu’ils ont faits, Apache Commons CaseInsensitiveMap
effectue pour vous le travail de personnalisation/spécialisation AbstractHashedMap
.
Mais si les opérations O(logN) get
et put
sont acceptables, un TreeMap
avec un comparateur de chaîne ne respectant pas la casse est une option; par exemple. using String.CASE_INSENSITIVE_ORDER
.
Et si cela ne vous dérange pas de créer un nouvel objet String temporaire chaque fois que vous faites un put
ou un get
, la réponse de Vishal convient parfaitement. (Cependant, je remarque que vous ne préserveriez pas le cas original des clés si vous le faisiez ...)
Sous-classe HashMap
et créez une version qui minuscule la clé sur put
et get
(et probablement les autres méthodes orientées clé).
Ou composez un HashMap
dans la nouvelle classe et déléguez tout à la carte, mais traduisez les clés.
Si vous devez conserver la clé d'origine, vous pouvez soit conserver une double carte, soit stocker la clé d'origine avec la valeur.
Deux choix me viennent à l’esprit:
s.toUpperCase().hashCode();
comme clé de la Map
.TreeMap<String>
avec un Comparator
personnalisé qui ignore la casse.Sinon, si vous préférez votre solution, au lieu de définir un nouveau type de chaîne, je préférerais implémenter une nouvelle carte avec la fonctionnalité d'insensibilité à la casse requise.
Vous pouvez utiliser un HashingStrategy basé sur Map
de Collections Eclipse
HashingStrategy<String> hashingStrategy =
HashingStrategies.fromFunction(String::toUpperCase);
MutableMap<String, String> node = HashingStrategyMaps.mutable.of(hashingStrategy);
Remarque: je contribue aux collections Eclipse.
Ne serait-il pas préférable de "boucler" la chaîne afin de mémoriser le hashCode. Dans la classe String normale, hashCode () correspond à O(N) pour la première fois, puis à O(1), car il est conservé pour une utilisation ultérieure.
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.
}
Cela vous permettrait d'utiliser n'importe quelle implémentation de Hashtable dans Java et d'avoir O(1) hasCode ().
Pour une implémentation robuste de CaseInsensitiveMap/CaseInsensitiveSet, consultez Java-util ( https://github.com/jdereg/Java-util ).
Ces cartes fonctionnent dans le temps de recherche standard O(1), conservent la casse des éléments ajoutés, prennent en charge toutes les API de carte comme putAll (), RetainAll (), removeAll () et permettent de placer des éléments hétérogènes. dans le jeu de clés.
De plus, les Java.util.Set retournés par .keySet () et .entrySet () respectent la non-sensibilité à la casse (beaucoup d'implémentations ne le font pas). Enfin, si vous extrayez la clé de l'ensemble clé/entrée lors de l'itération, vous obtenez une chaîne, et non une classe wrapper CaseInsensitiveString.
Sur la base d’autres réponses, il existe essentiellement deux approches: le sous-classement HashMap
ou l’emballage String
. Le premier nécessite un peu plus de travail. En fait, si vous voulez le faire correctement, vous devez remplacer presque toutes les méthodes (containsKey, entrySet, get, put, putAll and remove
).
Quoi qu'il en soit, il y a un problème. Si vous souhaitez éviter des problèmes futurs, vous devez spécifier une opération de cas Locale
in String
. Donc, vous créeriez de nouvelles méthodes (get(String, Locale)
, ...). Tout est plus simple et plus clair
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
}
Et bien, à propos de vos soucis de performance: l'optimisation prématurée est la racine de tout mal :)
Pour cette raison, je crée un nouvel objet CaseInsensitiveString pour chaque événement. Donc, cela pourrait affecter les performances.
La création de wrappers ou la conversion de clés en minuscules avant la recherche créent tous deux de nouveaux objets. Écrire votre propre implémentation Java.util.Map est le seul moyen d'éviter cela. Ce n'est pas trop difficile, et l'OMI en vaut la peine. J'ai trouvé que la fonction de hachage suivante fonctionnait plutôt bien, jusqu'à quelques centaines de clés.
static int ciHashCode(String string)
{
// length and the low 5 bits of hashCode() are case insensitive
return (string.hashCode() & 0x1f)*33 + string.length();
}
C'est un adaptateur pour HashMaps que j'ai implémenté pour un projet récent. Fonctionne d'une manière similaire à celle de @SandyR, mais encapsule la logique de conversion afin de ne pas convertir manuellement les chaînes en un objet wrapper.
J'ai utilisé Java 8 fonctionnalités, mais avec quelques modifications, vous pouvez l'adapter aux versions précédentes. Je l'ai testé pour la plupart des scénarios courants, à l'exception des nouvelles fonctions de flux Java 8.
Fondamentalement, il encapsule un HashMap, y dirige toutes les fonctions lors de la conversion de chaînes en/à partir d'un objet wrapper. Mais je devais également adapter KeySet et EntrySet car ils transmettaient certaines fonctions à la carte elle-même. Donc, je retourne deux nouveaux ensembles pour les clés et les entrées qui enveloppent réellement les keySet () et entrySet () d'origine.
Une remarque: Java 8 a changé l'implémentation de la méthode putAll que je ne pouvais pas trouver un moyen facile de remplacer. L'implémentation actuelle peut donc avoir dégradé les performances, en particulier si vous utilisez putAll () pour un grand ensemble de données.
S'il vous plaît laissez-moi savoir si vous trouvez un bug ou avez des suggestions pour améliorer le code.
package webbit.collections;
import Java.util.*;
import Java.util.function.*;
import Java.util.stream.Collectors;
import Java.util.stream.Stream;
import Java.util.stream.StreamSupport;
public class CaseInsensitiveMapAdapter<T> implements Map<String,T>
{
private Map<CaseInsensitiveMapKey,T> map;
private KeySet keySet;
private EntrySet entrySet;
public CaseInsensitiveMapAdapter()
{
}
public CaseInsensitiveMapAdapter(Map<String, T> map)
{
this.map = getMapImplementation();
this.putAll(map);
}
@Override
public int size()
{
return getMap().size();
}
@Override
public boolean isEmpty()
{
return getMap().isEmpty();
}
@Override
public boolean containsKey(Object key)
{
return getMap().containsKey(lookupKey(key));
}
@Override
public boolean containsValue(Object value)
{
return getMap().containsValue(value);
}
@Override
public T get(Object key)
{
return getMap().get(lookupKey(key));
}
@Override
public T put(String key, T value)
{
return getMap().put(lookupKey(key), value);
}
@Override
public T remove(Object key)
{
return getMap().remove(lookupKey(key));
}
/***
* I completely ignore Java 8 implementation and put one by one.This will be slower.
*/
@Override
public void putAll(Map<? extends String, ? extends T> m)
{
for (String key : m.keySet()) {
getMap().put(lookupKey(key),m.get(key));
}
}
@Override
public void clear()
{
getMap().clear();
}
@Override
public Set<String> keySet()
{
if (keySet == null)
keySet = new KeySet(getMap().keySet());
return keySet;
}
@Override
public Collection<T> values()
{
return getMap().values();
}
@Override
public Set<Entry<String, T>> entrySet()
{
if (entrySet == null)
entrySet = new EntrySet(getMap().entrySet());
return entrySet;
}
@Override
public boolean equals(Object o)
{
return getMap().equals(o);
}
@Override
public int hashCode()
{
return getMap().hashCode();
}
@Override
public T getOrDefault(Object key, T defaultValue)
{
return getMap().getOrDefault(lookupKey(key), defaultValue);
}
@Override
public void forEach(final BiConsumer<? super String, ? super T> action)
{
getMap().forEach(new BiConsumer<CaseInsensitiveMapKey, T>()
{
@Override
public void accept(CaseInsensitiveMapKey lookupKey, T t)
{
action.accept(lookupKey.key,t);
}
});
}
@Override
public void replaceAll(final BiFunction<? super String, ? super T, ? extends T> function)
{
getMap().replaceAll(new BiFunction<CaseInsensitiveMapKey, T, T>()
{
@Override
public T apply(CaseInsensitiveMapKey lookupKey, T t)
{
return function.apply(lookupKey.key,t);
}
});
}
@Override
public T putIfAbsent(String key, T value)
{
return getMap().putIfAbsent(lookupKey(key), value);
}
@Override
public boolean remove(Object key, Object value)
{
return getMap().remove(lookupKey(key), value);
}
@Override
public boolean replace(String key, T oldValue, T newValue)
{
return getMap().replace(lookupKey(key), oldValue, newValue);
}
@Override
public T replace(String key, T value)
{
return getMap().replace(lookupKey(key), value);
}
@Override
public T computeIfAbsent(String key, final Function<? super String, ? extends T> mappingFunction)
{
return getMap().computeIfAbsent(lookupKey(key), new Function<CaseInsensitiveMapKey, T>()
{
@Override
public T apply(CaseInsensitiveMapKey lookupKey)
{
return mappingFunction.apply(lookupKey.key);
}
});
}
@Override
public T computeIfPresent(String key, final BiFunction<? super String, ? super T, ? extends T> remappingFunction)
{
return getMap().computeIfPresent(lookupKey(key), new BiFunction<CaseInsensitiveMapKey, T, T>()
{
@Override
public T apply(CaseInsensitiveMapKey lookupKey, T t)
{
return remappingFunction.apply(lookupKey.key, t);
}
});
}
@Override
public T compute(String key, final BiFunction<? super String, ? super T, ? extends T> remappingFunction)
{
return getMap().compute(lookupKey(key), new BiFunction<CaseInsensitiveMapKey, T, T>()
{
@Override
public T apply(CaseInsensitiveMapKey lookupKey, T t)
{
return remappingFunction.apply(lookupKey.key,t);
}
});
}
@Override
public T merge(String key, T value, BiFunction<? super T, ? super T, ? extends T> remappingFunction)
{
return getMap().merge(lookupKey(key), value, remappingFunction);
}
protected Map<CaseInsensitiveMapKey,T> getMapImplementation() {
return new HashMap<>();
}
private Map<CaseInsensitiveMapKey,T> getMap() {
if (map == null)
map = getMapImplementation();
return map;
}
private CaseInsensitiveMapKey lookupKey(Object key)
{
return new CaseInsensitiveMapKey((String)key);
}
public class CaseInsensitiveMapKey {
private String key;
private String lookupKey;
public CaseInsensitiveMapKey(String key)
{
this.key = key;
this.lookupKey = key.toUpperCase();
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CaseInsensitiveMapKey that = (CaseInsensitiveMapKey) o;
return lookupKey.equals(that.lookupKey);
}
@Override
public int hashCode()
{
return lookupKey.hashCode();
}
}
private class KeySet implements Set<String> {
private Set<CaseInsensitiveMapKey> wrapped;
public KeySet(Set<CaseInsensitiveMapKey> wrapped)
{
this.wrapped = wrapped;
}
private List<String> keyList() {
return stream().collect(Collectors.toList());
}
private Collection<CaseInsensitiveMapKey> mapCollection(Collection<?> c) {
return c.stream().map(it -> lookupKey(it)).collect(Collectors.toList());
}
@Override
public int size()
{
return wrapped.size();
}
@Override
public boolean isEmpty()
{
return wrapped.isEmpty();
}
@Override
public boolean contains(Object o)
{
return wrapped.contains(lookupKey(o));
}
@Override
public Iterator<String> iterator()
{
return keyList().iterator();
}
@Override
public Object[] toArray()
{
return keyList().toArray();
}
@Override
public <T> T[] toArray(T[] a)
{
return keyList().toArray(a);
}
@Override
public boolean add(String s)
{
return wrapped.add(lookupKey(s));
}
@Override
public boolean remove(Object o)
{
return wrapped.remove(lookupKey(o));
}
@Override
public boolean containsAll(Collection<?> c)
{
return keyList().containsAll(c);
}
@Override
public boolean addAll(Collection<? extends String> c)
{
return wrapped.addAll(mapCollection(c));
}
@Override
public boolean retainAll(Collection<?> c)
{
return wrapped.retainAll(mapCollection(c));
}
@Override
public boolean removeAll(Collection<?> c)
{
return wrapped.removeAll(mapCollection(c));
}
@Override
public void clear()
{
wrapped.clear();
}
@Override
public boolean equals(Object o)
{
return wrapped.equals(lookupKey(o));
}
@Override
public int hashCode()
{
return wrapped.hashCode();
}
@Override
public Spliterator<String> spliterator()
{
return keyList().spliterator();
}
@Override
public boolean removeIf(Predicate<? super String> filter)
{
return wrapped.removeIf(new Predicate<CaseInsensitiveMapKey>()
{
@Override
public boolean test(CaseInsensitiveMapKey lookupKey)
{
return filter.test(lookupKey.key);
}
});
}
@Override
public Stream<String> stream()
{
return wrapped.stream().map(it -> it.key);
}
@Override
public Stream<String> parallelStream()
{
return wrapped.stream().map(it -> it.key).parallel();
}
@Override
public void forEach(Consumer<? super String> action)
{
wrapped.forEach(new Consumer<CaseInsensitiveMapKey>()
{
@Override
public void accept(CaseInsensitiveMapKey lookupKey)
{
action.accept(lookupKey.key);
}
});
}
}
private class EntrySet implements Set<Map.Entry<String,T>> {
private Set<Entry<CaseInsensitiveMapKey,T>> wrapped;
public EntrySet(Set<Entry<CaseInsensitiveMapKey,T>> wrapped)
{
this.wrapped = wrapped;
}
private List<Map.Entry<String,T>> keyList() {
return stream().collect(Collectors.toList());
}
private Collection<Entry<CaseInsensitiveMapKey,T>> mapCollection(Collection<?> c) {
return c.stream().map(it -> new CaseInsensitiveEntryAdapter((Entry<String,T>)it)).collect(Collectors.toList());
}
@Override
public int size()
{
return wrapped.size();
}
@Override
public boolean isEmpty()
{
return wrapped.isEmpty();
}
@Override
public boolean contains(Object o)
{
return wrapped.contains(lookupKey(o));
}
@Override
public Iterator<Map.Entry<String,T>> iterator()
{
return keyList().iterator();
}
@Override
public Object[] toArray()
{
return keyList().toArray();
}
@Override
public <T> T[] toArray(T[] a)
{
return keyList().toArray(a);
}
@Override
public boolean add(Entry<String,T> s)
{
return wrapped.add(null );
}
@Override
public boolean remove(Object o)
{
return wrapped.remove(lookupKey(o));
}
@Override
public boolean containsAll(Collection<?> c)
{
return keyList().containsAll(c);
}
@Override
public boolean addAll(Collection<? extends Entry<String,T>> c)
{
return wrapped.addAll(mapCollection(c));
}
@Override
public boolean retainAll(Collection<?> c)
{
return wrapped.retainAll(mapCollection(c));
}
@Override
public boolean removeAll(Collection<?> c)
{
return wrapped.removeAll(mapCollection(c));
}
@Override
public void clear()
{
wrapped.clear();
}
@Override
public boolean equals(Object o)
{
return wrapped.equals(lookupKey(o));
}
@Override
public int hashCode()
{
return wrapped.hashCode();
}
@Override
public Spliterator<Entry<String,T>> spliterator()
{
return keyList().spliterator();
}
@Override
public boolean removeIf(Predicate<? super Entry<String, T>> filter)
{
return wrapped.removeIf(new Predicate<Entry<CaseInsensitiveMapKey, T>>()
{
@Override
public boolean test(Entry<CaseInsensitiveMapKey, T> entry)
{
return filter.test(new FromCaseInsensitiveEntryAdapter(entry));
}
});
}
@Override
public Stream<Entry<String,T>> stream()
{
return wrapped.stream().map(it -> new Entry<String, T>()
{
@Override
public String getKey()
{
return it.getKey().key;
}
@Override
public T getValue()
{
return it.getValue();
}
@Override
public T setValue(T value)
{
return it.setValue(value);
}
});
}
@Override
public Stream<Map.Entry<String,T>> parallelStream()
{
return StreamSupport.stream(spliterator(), true);
}
@Override
public void forEach(Consumer<? super Entry<String, T>> action)
{
wrapped.forEach(new Consumer<Entry<CaseInsensitiveMapKey, T>>()
{
@Override
public void accept(Entry<CaseInsensitiveMapKey, T> entry)
{
action.accept(new FromCaseInsensitiveEntryAdapter(entry));
}
});
}
}
private class EntryAdapter implements Map.Entry<String,T> {
private Entry<String,T> wrapped;
public EntryAdapter(Entry<String, T> wrapped)
{
this.wrapped = wrapped;
}
@Override
public String getKey()
{
return wrapped.getKey();
}
@Override
public T getValue()
{
return wrapped.getValue();
}
@Override
public T setValue(T value)
{
return wrapped.setValue(value);
}
@Override
public boolean equals(Object o)
{
return wrapped.equals(o);
}
@Override
public int hashCode()
{
return wrapped.hashCode();
}
}
private class CaseInsensitiveEntryAdapter implements Map.Entry<CaseInsensitiveMapKey,T> {
private Entry<String,T> wrapped;
public CaseInsensitiveEntryAdapter(Entry<String, T> wrapped)
{
this.wrapped = wrapped;
}
@Override
public CaseInsensitiveMapKey getKey()
{
return lookupKey(wrapped.getKey());
}
@Override
public T getValue()
{
return wrapped.getValue();
}
@Override
public T setValue(T value)
{
return wrapped.setValue(value);
}
}
private class FromCaseInsensitiveEntryAdapter implements Map.Entry<String,T> {
private Entry<CaseInsensitiveMapKey,T> wrapped;
public FromCaseInsensitiveEntryAdapter(Entry<CaseInsensitiveMapKey, T> wrapped)
{
this.wrapped = wrapped;
}
@Override
public String getKey()
{
return wrapped.getKey().key;
}
@Override
public T getValue()
{
return wrapped.getValue();
}
@Override
public T setValue(T value)
{
return wrapped.setValue(value);
}
}
}