HashMap 키와 같이 대소 문자를 구분하지 않는 문자열
다음과 같은 이유로 대소 문자를 구분하지 않는 문자열을 HashMap 키로 사용하고 싶습니다.
- 초기화하는 동안 내 프로그램은 사용자 정의 문자열로 HashMap을 만듭니다.
- 이벤트 (내 경우에는 네트워크 트래픽)를 처리하는 동안 다른 경우에는 String을 받았을 수도 있지만
<key, value>
트래픽에서받은 케이스를 무시하고 HashMap에서 from 을 찾을 수 있어야 합니다.
나는이 접근법을 따랐다.
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의 새 객체를 만들고 있습니다. 따라서 성능이 저하 될 수 있습니다.
이 문제를 해결하는 다른 방법이 있습니까?
Map<String, String> nodeMap =
new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
그게 당신이 필요한 전부입니다.
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());
}
}
또는
한 가지 방법은 Apache Commons AbstractHashedMap
클래스 의 사용자 정의 서브 클래스를 작성하여 대소 문자를 구분하지 않는 해시 및 키 비교를 수행하기 위해 hash
및 isEqualKeys
메소드를 대체하는 것입니다. (참고-나는 이것을 직접 시도한 적이 없다 ...)
이렇게하면 맵 조회 또는 업데이트를 수행 할 때마다 새 객체를 생성하는 오버 헤드가 발생하지 않습니다. 그리고 일반적인 Map
작업은 일반처럼 O (1) ...이어야합니다 HashMap
.
그리고 그들이 선택한 구현 선택을 받아 들일 준비가 되었다면 Apache Commons CaseInsensitiveMap
는 사용자 정의 / 전문화 작업 AbstractHashedMap
을 수행합니다.
그러나 O (logN) get
및 put
연산이 허용되는 TreeMap
경우 대소 문자를 구분하지 않는 문자열 비교기가 옵션입니다. 예를 들어 String.CASE_INSENSITIVE_ORDER
.
새 임시 String 객체에게 당신이 할 때마다 생성 괜찮다면 그리고 put
나 get
, 다음 이씨의 대답은 잘합니다. (하지만, 그렇게 한 경우 원래 키 케이스를 보존하지 않을 것입니다 ...)
서브 클래스는 HashMap
그 낮은 경우상의 키 버전을 생성 put
하고 get
(그리고 아마도 다른 키 지향 방법).
또는 a HashMap
를 새 클래스에 합성하고 모든 것을지도에 위임하지만 키를 번역하십시오.
원래 키를 유지해야하는 경우 이중 맵을 유지하거나 원래 키를 값과 함께 저장할 수 있습니다.
두 가지 선택이 떠 오릅니다.
s.toUpperCase().hashCode();
의 키로 직접을 사용할 수 있습니다Map
.- 대소 문자를 무시
TreeMap<String>
하는 사용자 정의와 함께를 사용할 수 있습니다Comparator
.
그렇지 않으면 새로운 종류의 문자열을 정의하는 대신 솔루션을 선호하는 경우 필요한 대소 문자를 구분하지 않는 기능으로 새 맵을 구현합니다.
해시 코드를 기억하기 위해 문자열을 "랩핑"하는 것이 좋지 않을 것입니다. 일반적인 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에서 Hashtable의 모든 구현을 사용하고 O (1) hasCode ()를 가질 수 있습니다.
Eclipse Collections 에서 HashingStrategy 를 사용할 수 있습니다Map
HashingStrategy<String> hashingStrategy =
HashingStrategies.fromFunction(String::toUpperCase);
MutableMap<String, String> node = HashingStrategyMaps.mutable.of(hashingStrategy);
참고 : 저는 Eclipse Collections에 기고자입니다.
서브 클래스 : 다른 답변을 바탕으로 두 가지 접근 방법에는 기본적으로 HashMap
또는 포장 String
. 첫 번째는 조금 더 많은 작업이 필요합니다. 실제로 올바르게 수행하려면 거의 모든 메소드 ( containsKey, entrySet, get, put, putAll and remove
)를 대체해야합니다 .
어쨌든 문제가 있습니다. 당신은 미래의 문제를 방지하려는 경우를 지정해야합니다 Locale
의 String
경우 작업. 따라서 새로운 메소드 ( get(String, Locale)
, ...)를 만들 것 입니다. 모든 것이 더 쉽고 명확하게 줄 바꿈됩니다.
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
}
그리고 성능에 대한 걱정에 대해 : 조기 최적화는 모든 악의 근원입니다 :)
강력한 CaseInsensitiveMap / CaseInsensitiveSet 구현에 대해서는 java-util ( https://github.com/jdereg/java-util )을 확인하십시오 .
이지도는 표준 O (1) 조회 시간에 수행하고 추가 된 항목의 대소 문자를 유지하며 putAll (), retainAll (), removeAll ()과 같은 모든 Map API를 지원하며 이기종 항목을 키 세트에 배치 할 수 있습니다.
또한 java.util.Set은 .keySet () 및 .entrySet ()에 의해 대소 문자를 구분하지 않습니다 (많은 구현은 그렇지 않습니다). 마지막으로 반복하는 동안 키 / 항목 세트에서 키를 가져 오면 CaseInsensitiveString 래퍼 클래스가 아닌 String을 다시 얻습니다.
이것은 최근 프로젝트를 위해 구현 한 HashMaps 용 어댑터입니다. @SandyR과 비슷한 방식으로 작동하지만 변환 논리를 캡슐화하여 문자열을 래퍼 객체로 수동으로 변환하지 않습니다.
Java 8 기능을 사용했지만 약간의 변경만으로 이전 버전에 맞게 조정할 수 있습니다. 새로운 Java 8 스트림 기능을 제외하고 가장 일반적인 시나리오에서 테스트했습니다.
기본적으로 HashMap을 래핑하고 문자열을 래퍼 객체로 변환하거나 래퍼 객체에서 모든 함수를 전달합니다. 그러나 KeySet 및 EntrySet도 일부 기능을 맵 자체에 전달하기 때문에 적응해야했습니다. 따라서 원래 keySet () 및 entrySet ()을 실제로 래핑하는 키와 항목에 대해 두 개의 새로운 세트를 반환합니다.
참고 사항 : Java 8은 putAll 메소드의 구현을 변경하여 쉽게 재정의하는 방법을 찾을 수 없었습니다. 따라서 대규모 데이터 세트에 putAll ()을 사용하는 경우 현재 구현에서 성능이 저하 될 수 있습니다.
버그를 발견하거나 코드 개선을위한 제안 사항이 있으면 알려주십시오.
패키지 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);
}
}
}
이 때문에 모든 이벤트에 대해 CaseInsensitiveString의 새 객체를 만들고 있습니다. 따라서 성능이 저하 될 수 있습니다.
조회하기 전에 랩퍼를 작성하거나 키를 소문자로 변환하면 둘 다 새 오브젝트를 작성합니다. 자신의 java.util.Map 구현을 작성하는 것이 이것을 피할 수있는 유일한 방법입니다. 너무 어렵지 않고 IMO는 그만한 가치가 있습니다. 다음 해시 함수가 최대 수백 개의 키까지 잘 작동한다는 것을 알았습니다.
static int ciHashCode(String string)
{
// length and the low 5 bits of hashCode() are case insensitive
return (string.hashCode() & 0x1f)*33 + string.length();
}
Java 8 스트림을 사용하는 것은 어떻습니까.
nodeMap.entrySet().stream().filter(x->x.getKey().equalsIgnoreCase(stringfromEven.toString()).collect(Collectors.toList())
참고 URL : https://stackoverflow.com/questions/8236945/case-insensitive-string-as-hashmap-key
'development' 카테고리의 다른 글
jQuery에서 select 태그의 옵션 태그 수 계산 (0) | 2020.06.10 |
---|---|
파이썬의 사전에서 모든 값을 추출하려면 어떻게합니까? (0) | 2020.06.10 |
Spring MVC : 유효성 검사를 수행하는 방법? (0) | 2020.06.10 |
기본 Android 애플리케이션에서“Font Awesome”의 아이콘 및 기호를 사용하는 방법 (0) | 2020.06.10 |
숫자 값으로 루비 해시를 정렬하는 방법? (0) | 2020.06.10 |