我有一个有序的LinkedHashMap,希望在指定的位置添加元素,例如在地图中的第一个或最后一个位置。如何在LinkedHashMap中添加元素到特定位置?
即使只能将元素添加到LinkedHashMap的第一个或最后一个位置也可以!
我有一个有序的LinkedHashMap,希望在指定的位置添加元素,例如在地图中的第一个或最后一个位置。如何在LinkedHashMap中添加元素到特定位置?
即使只能将元素添加到LinkedHashMap的第一个或最后一个位置也可以!
您可以将此元素添加到1或最后位置:
添加到最后位置 ► 您只需像这样从地图中删除前一个条目:
map.remove(key);
map.put(key,value);
将值添加到第一位 ► 这有点复杂,你需要克隆地图、清空它、将1. 值放入其中,并将新地图放入其中,就像这样:
我使用的是字符串键和组(我的自定义类)值的地图:
LinkedHashMap<String, Group> newMap=(LinkedHashMap<String, Group>) map.clone();
map.clear();
map.put(key, value);
map.putAll(newMap);
如您所见,使用这些方法您可以将无限数量的元素添加到地图的开头和结尾。
insert-order
(默认)或access-order
与此构造函数:
请参阅:
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
构造一个带有指定初始容量、负载因子和排序模式的空LinkedHashMap实例。
参数: initialCapacity - 初始容量 loadFactor - 负载因子 accessOrder - 排序模式 - true为访问顺序,false为插入顺序
抛出: IllegalArgumentException - 如果初始容量为负数或负载因子为非正数
LinkedHashMap
由于JDK的LinkedHashMap只能保证按照插入顺序检索,如果我们想要按照索引插入,我们可以使用Apache Commons的ListOrderedMap作为替代方案。它的实现原理很简单 - 通过维护列表来保持插入顺序并赋予相应的索引,同时也像通常一样插入普通映射。这是文档中所述:
public class ListOrderedMap<K,V> extends AbstractMapDecorator<K,V> implements OrderedMap<K,V>, Serializable
使用列表来保持顺序,装饰一个
Map
以确保添加顺序得以保留。顺序将通过迭代器和视图中的
toArray
方法使用。顺序也由MapIterator
返回。orderedMapIterator()
方法访问一个可以前后遍历映射的迭代器。此外,提供了非接口方法以通过索引访问映射。如果一个对象第二次被添加到Map中,它将保留在迭代中的原始位置。
请注意,
ListOrderedMap
不是同步的,也不是线程安全的。如果您希望从多个线程同时使用此Map,必须使用适当的同步。最简单的方法是使用Collections.synchronizedMap(Map)
包装此Map。当未同步地访问此类时,可能会抛出异常。请注意,
ListOrderedMap
不能与IdentityHashMap
、CaseInsensitiveMap
或违反Map
通用合同的类似映射一起使用。ListOrderedMap
(更精确地说是其基础List
)依赖于equals()
。只要修饰的Map
也是基于equals()
和hashCode()
的,这是可行的,而IdentityHashMap
和CaseInsensitiveMap
则不是:前者使用==
,后者在小写键上使用equals()
。下面是将元素添加到特定位置的实现:
/** 428 * Puts a key-value mapping into the map at the specified index. 429 * <p> 430 * If the map already contains the key, then the original mapping 431 * is removed and the new mapping added at the specified index. 432 * The remove may change the effect of the index. The index is 433 * always calculated relative to the original state of the map. 434 * <p> 435 * Thus the steps are: (1) remove the existing key-value mapping, 436 * then (2) insert the new key-value mapping at the position it 437 * would have been inserted had the remove not occurred. 438 * 439 * @param index the index at which the mapping should be inserted 440 * @param key the key 441 * @param value the value 442 * @return the value previously mapped to the key 443 * @throws IndexOutOfBoundsException if the index is out of range [0, size] 444 * @since 3.2 445 */ 446 public V put(int index, final K key, final V value) { 447 if (index < 0 || index > insertOrder.size()) { 448 throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + insertOrder.size()); 449 } 450 451 final Map<K, V> m = decorated(); 452 if (m.containsKey(key)) { 453 final V result = m.remove(key); 454 final int pos = insertOrder.indexOf(key); 455 insertOrder.remove(pos); 456 if (pos < index) { 457 index--; 458 } 459 insertOrder.add(index, key); 460 m.put(key, value); 461 return result; 462 } 463 insertOrder.add(index, key); 464 m.put(key, value); 465 return null; 466 }
public static <K, V> void add(LinkedHashMap<K, V> map, int index, K key, V value) {
assert (map != null);
assert !map.containsKey(key);
assert (index >= 0) && (index < map.size());
int i = 0;
List<Entry<K, V>> rest = new ArrayList<Entry<K, V>>();
for (Entry<K, V> entry : map.entrySet()) {
if (i++ >= index) {
rest.add(entry);
}
}
map.put(key, value);
for (int j = 0; j < rest.size(); j++) {
Entry<K, V> entry = rest.get(j);
map.remove(entry.getKey());
map.put(entry.getKey(), entry.getValue());
}
}
只需将你的LinkedHashMap
分成两个数组。第一个数组的大小为index - 1
,并在末尾放入新的Entry
。然后用第二个数组中的条目填充第一个数组。
...
LinkedHashMap<String, StringExtension> map = new LinkedHashMap<>();
map.put("4", new StringExtension("4", "a"));
map.put("1", new StringExtension("1", "b"));
map.put("3", new StringExtension("3", "c"));
map.put("2", new StringExtension("2", "d"));
for (Map.Entry<String, StringExtension> entry : map.entrySet()) {
Log.e("Test", "" + entry.getKey() + " - "+ entry.getValue().value);
}
Collection<StringExtension> temp = new ArrayList<>(map.values());
StringExtension value = map.remove("3");
map.clear();
map.put(value.key, value);
for (StringExtension val : temp) {
map.put(val.key, val);
}
Log.e("Test", "---");
for (Map.Entry<String, StringExtension> entry : map.entrySet()) {
Log.e("Test", "" + entry.getKey() + " - "+ entry.getValue().value);
}
...
private class StringExtension
{
String key;
String value;
public StringExtension(String key, String value) {
this.key = key;
this.value = value;
}
}
Map
在迭代时没有定义条目的顺序,因此LinkedHashMap
是对该合同的一种面向对象的正确实现和完善。所提出的问题是该合同是否扩展到将插入项插入链表的其他位置。因此,声称Map
不应允许控制插入位置的论点无效。 - Dilum Ranatunga正如其他答案所建议的那样,通过插入一个值来修改LinkedHashMap并不是推荐的做法。
为什么?
因为LinkedHashMap由HashMap和LinkedList支持。
后者用于保留插入顺序。
很显然,LinkedList在插入到头部和尾部(双向链接)方面非常好。
它提供了O(1)的时间复杂度。
然而,将元素插入到特定位置会非常慢,因为你需要遍历整个列表才能找到这个位置,在最坏的情况下需要O(n)的时间复杂度,对于大型数据集来说代价相当高。
我个人不建议这样做,因为它效率低下,
但如果你理解了缺点并且确实需要这样做,
你可以使用这个扩展函数,以将键值对插入到特定位置。
fun <K, V> LinkedHashMap<K, V>.insertAtIndex(key: K, value: V, index: Int) {
if (index < 0) {
throw IllegalArgumentException("Cannot insert into negative index")
}
if (index > size) {
put(key, value)
return
}
val holderMap = LinkedHashMap<K, V>(size + 1)
entries.forEachIndexed { i, entry->
if (i == index) {
holderMap.put(key, value)
}
holderMap.put(entry.key, entry.value)
}
clear()
putAll(holderMap)
}
它将花费O(n)的时间和O(n)的空间。
Map
的数据来自List
(集合),那么您应该创建一个反向的循环,从该列表的末尾开始读取,直到它到达第一个,如下所示:
for (int key = collection.size()-1; key > map.size(); key--) { Data data = collection.get(key); map.put(key, data); }
可以使用 putFirst()
和 putLast()
方法将元素添加为第一个或最后一个元素,这些方法将在 Java 21 中作为顺序集合增强功能被添加到 LinkedHashMap
中。
LinkedHashMap<String, Integer> linkedHashMap = // ...
linkedHashMap.put("A", 1);
linkedHashMap.putLast("B", 2);
linkedHashMap.putFirst("C", 3);
// linkedHashMap is now [C=3, A=1, B=2]