场景
使用LinkedHashMap时逆序删除元素
代码
Map<String, String> map = new LinkedHashMap<>();
map.put("1_1", "1");
map.put("1_2", "1");
map.put("1_3", "1");
map.put("2_1", "2");
map.put("2_2", "2");
map.put("2_3", "2");
List<Map.Entry<String, String>> list = new ArrayList<>(map.entrySet());
ListIterator<Map.Entry<String, String>> iterator = list.listIterator(list.size());
while (iterator.hasPrevious()) {
Map.Entry<String, String> entry = iterator.previous();
String key = entry.getKey();
if (key != null && key.startsWith("2")) {
iterator.remove();
}
}
System.out.println(list);
System.out.println(map);
输出
[1_1=1, 1_2=1, 1_3=1]
{1_1=1, 1_2=1, 1_3=1, 2_1=2, 2_2=2, 2_3=2}
分析
为了逆序地删除map中的元素,首先用了LinkedHashMap来存元素,通过底层的双向链表结构来保证有序性;其次,为了使用List的双向迭代器listIterator,把Map.Entry的Set集合添加到ArrayList中,最后通过listIterator由后向前删除key为2开头的元素,最后打印结果发现只有list中的元素被删了,map中的元素没有变
问题出在哪里?
猜想一:深拷贝
首先会想到会不会是深拷贝的问题,也就是说如果listIterator中的Map.Entry结构中的元素地址与原来的不一样,那么这个现象就可以解释了
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}
阅读源码发现new ArrayList(Collection<? extends E> c)底层使用System.arraycopy来做数组拷贝,属于浅拷贝
猜想二:迭代器
那么继续阅读迭代器的源码,首先看LinkedHashMap自己的迭代器
final class LinkedEntrySet extends AbstractSet<Map.Entry<K,V>> {
public final int size() { return size; }
public final void clear() { LinkedHashMap.this.clear(); }
public final Iterator<Map.Entry<K,V>> iterator() {
return new LinkedEntryIterator();
}
...
}
public final boolean remove(Object o) {
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Object value = e.getValue();
return removeNode(hash(key), key, value, true, true) != null;
}
return false;
}
...
}
发现remove方法最终会回调继承自HashMap的removeNode方法,这个方法才是真正的去map的底层数据结构中删除元素
同样看下ArrayList的双向迭代器listIterator源码
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
...
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
...
}
它同样会回调ArrayList当前对象的remove方法
结论
至此,疑团解开,开头的代码中试图使用list中的迭代器删除map底层数据结构中的元素,当然是行不通的。究其原因是对entryset这个东西的理解不深,它更像是当前map结构的视图,并不直接持有底层数据结构的引用,最终使用迭代器来删除仍然是回调remove底层数据结构的方法
最后,代码的修改方法也很简单,使用Map中的V remove(Object key)接口来删就可以了
参考
How to Iterate LinkedHashMap in Reverse Order in Java? - GeeksforGeeks