我对Java比较陌生,经常发现需要对值进行Map<Key,Value>排序。
由于这些值不是唯一的,我发现自己将keySet转换为一个数组,并使用自定义比较器通过数组排序对该数组进行排序,该比较器根据与该键关联的值进行排序。
有没有更简单的方法?
我对Java比较陌生,经常发现需要对值进行Map<Key,Value>排序。
由于这些值不是唯一的,我发现自己将keySet转换为一个数组,并使用自定义比较器通过数组排序对该数组进行排序,该比较器根据与该键关联的值进行排序。
有没有更简单的方法?
当前回答
这个问题已经有了很多答案,但没有一个能为我提供我想要的,一个返回按关联值排序的键和条目的映射实现,并在映射中修改键和值时维护这个属性。另外两个问题对此提出了具体要求。
我编写了一个通用友好的示例来解决这个用例。此实现不遵守Map接口的所有约定,例如反映原始对象中keySet()和entrySet()返回的集合中的值更改和删除。我觉得这样的解决方案太大,无法包含在堆栈溢出的答案中。如果我成功地创建了一个更完整的实现,也许我会将其发布到Github,然后在这个答案的更新版本中链接到它。
import java.util.*;
/**
* A map where {@link #keySet()} and {@link #entrySet()} return sets ordered
* by associated values based on the the comparator provided at construction
* time. The order of two or more keys with identical values is not defined.
* <p>
* Several contracts of the Map interface are not satisfied by this minimal
* implementation.
*/
public class ValueSortedMap<K, V> extends HashMap<K, V> {
protected Map<V, Collection<K>> valueToKeysMap;
// uses natural order of value object, if any
public ValueSortedMap() {
this((Comparator<? super V>) null);
}
public ValueSortedMap(Comparator<? super V> valueComparator) {
this.valueToKeysMap = new TreeMap<V, Collection<K>>(valueComparator);
}
public boolean containsValue(Object o) {
return valueToKeysMap.containsKey(o);
}
public V put(K k, V v) {
V oldV = null;
if (containsKey(k)) {
oldV = get(k);
valueToKeysMap.get(oldV).remove(k);
}
super.put(k, v);
if (!valueToKeysMap.containsKey(v)) {
Collection<K> keys = new ArrayList<K>();
keys.add(k);
valueToKeysMap.put(v, keys);
} else {
valueToKeysMap.get(v).add(k);
}
return oldV;
}
public void putAll(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
put(e.getKey(), e.getValue());
}
public V remove(Object k) {
V oldV = null;
if (containsKey(k)) {
oldV = get(k);
super.remove(k);
valueToKeysMap.get(oldV).remove(k);
}
return oldV;
}
public void clear() {
super.clear();
valueToKeysMap.clear();
}
public Set<K> keySet() {
LinkedHashSet<K> ret = new LinkedHashSet<K>(size());
for (V v : valueToKeysMap.keySet()) {
Collection<K> keys = valueToKeysMap.get(v);
ret.addAll(keys);
}
return ret;
}
public Set<Map.Entry<K, V>> entrySet() {
LinkedHashSet<Map.Entry<K, V>> ret = new LinkedHashSet<Map.Entry<K, V>>(size());
for (Collection<K> keys : valueToKeysMap.values()) {
for (final K k : keys) {
final V v = get(k);
ret.add(new Map.Entry<K,V>() {
public K getKey() {
return k;
}
public V getValue() {
return v;
}
public V setValue(V v) {
throw new UnsupportedOperationException();
}
});
}
}
return ret;
}
}
其他回答
使用通用比较器,例如:
final class MapValueComparator<K,V extends Comparable<V>> implements Comparator<K> {
private final Map<K,V> map;
private MapValueComparator() {
super();
}
public MapValueComparator(Map<K,V> map) {
this();
this.map = map;
}
public int compare(K o1, K o2) {
return map.get(o1).compareTo(map.get(o2));
}
}
Java 8提供了一个新的答案:将条目转换为流,并使用Map中的比较器组合符。条目:
Stream<Map.Entry<K,V>> sorted =
map.entrySet().stream()
.sorted(Map.Entry.comparingByValue());
这将允许您使用按值升序排序的条目。如果您想要递减值,只需反转比较器:
Stream<Map.Entry<K,V>> sorted =
map.entrySet().stream()
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue()));
如果这些值不可比较,则可以传递显式比较器:
Stream<Map.Entry<K,V>> sorted =
map.entrySet().stream()
.sorted(Map.Entry.comparingByValue(comparator));
然后,您可以继续使用其他流操作来使用数据。例如,如果要在新地图中显示前10名:
Map<K,V> topTen =
map.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.limit(10)
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
上面看到的LinkedHashMap按插入顺序迭代条目。
或打印到System.out:
map.entrySet().stream()
.sorted(Map.Entry.comparingByValue())
.forEach(System.out::println);
如果倾向于使用一个Map数据结构,该结构可以按值进行固有排序,而不必触发任何排序方法或显式传递给实用程序,则以下解决方案可能适用:
(1) org.rools.chance.core.util.ValueSortedMap(JBoss项目)在内部维护两个映射,一个用于查找,另一个用于维护排序值。与之前添加的答案非常相似,但可能是抽象和封装部分(包括复制机制)使其更安全地从外部使用。
(2) http://techblog.molindo.at/2008/11/java-map-sorted-by-value.html避免维护两个映射,而是依赖/扩展Apache Common的LinkedMap。(博客作者注:这里的所有代码都在公共领域):
// required to access LinkEntry.before and LinkEntry.after
package org.apache.commons.collections.map;
// SNIP: imports
/**
* map implementation based on LinkedMap that maintains a sorted list of
* values for iteration
*/
public class ValueSortedHashMap extends LinkedMap {
private final boolean _asc;
// don't use super()!
public ValueSortedHashMap(final boolean asc) {
super(DEFAULT_CAPACITY);
_asc = asc;
}
// SNIP: some more constructors with initial capacity and the like
protected void addEntry(final HashEntry entry, final int hashIndex) {
final LinkEntry link = (LinkEntry) entry;
insertSorted(link);
data[hashIndex] = entry;
}
protected void updateEntry(final HashEntry entry, final Object newValue) {
entry.setValue(newValue);
final LinkEntry link = (LinkEntry) entry;
link.before.after = link.after;
link.after.before = link.before;
link.after = link.before = null;
insertSorted(link);
}
private void insertSorted(final LinkEntry link) {
LinkEntry cur = header;
// iterate whole list, could (should?) be replaced with quicksearch
// start at end to optimize speed for in-order insertions
while ((cur = cur.before) != header & amp; & amp; !insertAfter(cur, link)) {}
link.after = cur.after;
link.before = cur;
cur.after.before = link;
cur.after = link;
}
protected boolean insertAfter(final LinkEntry cur, final LinkEntry link) {
if (_asc) {
return ((Comparable) cur.getValue())
.compareTo((V) link.getValue()) & lt; = 0;
} else {
return ((Comparable) cur.getValue())
.compareTo((V) link.getValue()) & gt; = 0;
}
}
public boolean isAscending() {
return _asc;
}
}
(3) 编写一个自定义映射或从LinkedHashMap扩展,该映射仅在枚举期间根据需要进行排序(例如,values()、keyset()、entryset())。内部实现/行为是从使用该类的实现/行为中抽象出来的,但在该类的客户端看来,当请求枚举时,值总是被排序的。如果所有的put操作都在枚举之前完成,这个类希望排序只发生一次。排序方法采用了前面对这个问题的一些回答。
public class SortByValueMap<K, V> implements Map<K, V> {
private boolean isSortingNeeded = false;
private final Map<K, V> map = new LinkedHashMap<>();
@Override
public V put(K key, V value) {
isSortingNeeded = true;
return map.put(key, value);
}
@Override
public void putAll(Map<? extends K, ? extends V> map) {
isSortingNeeded = true;
map.putAll(map);
}
@Override
public Set<K> keySet() {
sort();
return map.keySet();
}
@Override
public Set<Entry<K, V>> entrySet() {
sort();
return map.entrySet();
}
@Override
public Collection<V> values() {
sort();
return map.values();
}
private void sort() {
if (!isSortingNeeded) {
return;
}
List<Entry<K, V>> list = new ArrayList<>(size());
for (Iterator<Map.Entry<K, V>> it = map.entrySet().iterator(); it.hasNext();) {
Map.Entry<K, V> entry = it.next();
list.add(entry);
it.remove();
}
Collections.sort(list);
for (Entry<K, V> entry : list) {
map.put(entry.getKey(), entry.getValue());
}
isSortingNeeded = false;
}
@Override
public String toString() {
sort();
return map.toString();
}
}
(4) Guava提供了ImmutableMap.Builder.orderEntriesByValue(Comparator valueComparator),尽管生成的映射是不可变的:
将此生成器配置为根据指定的比较器。排序顺序是稳定的,也就是说,如果两个条目的值作为等价项进行比较,首先插入的条目将是第一个按照构建映射的迭代顺序。
我重写了devinmore的方法,该方法在不使用迭代器的情况下,根据地图的值对其进行排序:
public static Map<K, V> sortMapByValue(Map<K, V> inputMap) {
Set<Entry<K, V>> set = inputMap.entrySet();
List<Entry<K, V>> list = new ArrayList<Entry<K, V>>(set);
Collections.sort(list, new Comparator<Map.Entry<K, V>>()
{
@Override
public int compare(Entry<K, V> o1, Entry<K, V> o2) {
return (o1.getValue()).compareTo( o2.getValue() ); //Ascending order
}
} );
Map<K, V> sortedMap = new LinkedHashMap<>();
for(Map.Entry<K, V> entry : list){
sortedMap.put(entry.getKey(), entry.getValue());
}
return sortedMap;
}
注意:我们使用LinkedHashMap作为输出映射,因为我们的列表已经按值排序,现在我们应该按照插入键值的顺序将列表存储到输出映射中。因此,如果您使用例如TreeMap作为输出地图,您的地图将再次按地图键排序!
这是主要方法:
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("3", "three");
map.put("1", "one");
map.put("5", "five");
System.out.println("Input Map:" + map);
System.out.println("Sorted Map:" + sortMapByValue(map));
}
最后,这是输出:
Input Map:{1=one, 3=three, 5=five}
Sorted Map:{5=five, 1=one, 3=three}
从…起http://www.programmersheaven.com/download/49349/download.aspx
private static <K, V> Map<K, V> sortByValue(Map<K, V> map) {
List<Entry<K, V>> list = new LinkedList<>(map.entrySet());
Collections.sort(list, new Comparator<Object>() {
@SuppressWarnings("unchecked")
public int compare(Object o1, Object o2) {
return ((Comparable<V>) ((Map.Entry<K, V>) (o1)).getValue()).compareTo(((Map.Entry<K, V>) (o2)).getValue());
}
});
Map<K, V> result = new LinkedHashMap<>();
for (Iterator<Entry<K, V>> it = list.iterator(); it.hasNext();) {
Map.Entry<K, V> entry = (Map.Entry<K, V>) it.next();
result.put(entry.getKey(), entry.getValue());
}
return result;
}