list/map中安全删除元素

本文探讨了C++中list容器安全删除元素的方法,详细解释了如何避免因删除导致的迭代器失效问题,并给出了正确的删除操作示例。


最近遇到在map中删除元素安全性的问题,于是查了查资料,记录一下。

问题的引出:一个关于list::erase()的问题。

    写了个小程序试验list在erase一个成员之后iterator 值的变化,程序中注释部分是运行结果显示的数据,代码如下(主要代码):
 
list<CStudent*>::iterator It;  
  
for(It = myList.begin(); It != myList.end(); )  
  
{  
  
 cout<<"It= "<<&(*It)<<endl;     // It= 0x00372320  
  
 myList.erase(It);  
  
 cout<<"It= "<<&(*It)<<endl;     // It= 0x00372320  
  
 It++;    
  
 cout<<"It= "<<&(*It)<<endl;     // It= 0xDDDDDDE5  
  
}


问题:
     It++之后为什么It值由0x00372320变为0xDDDDDDE5而不是指向下一个元素地址?
答案:
     看看数据结构就明白了,每一个节点包括数据部分和指针部分,指针指向下一个节点,如果iterator指向当前节点,移除当前元素之后,下一个元素的地址也丢失了,所以It++时找不到下一个元素。

移除list中的元素:

      List提供了两个函数用于移除成员:erase()和remove(),关于两个函数的用法可以参考MSDN,这里重点讲述用这两个函数时需要注意的问题。

注意点一、删除全部元素


错误写法:

for(It = myList.begin(); It != myList.end(); It++)  
{  
	myList.erase(It);  
}  

    本人刚开始就犯了上面这样的错误,学过数据结构的朋友知道,链表中的每个节点包括两部分,数据部分和指针部分,指针指向下一个节点的地址,当It指向当前节点并对当前节点进行删除操作时,下一节点的地址就丢失了,多么可怕! 

正确写法:

for(It = m_MyList.begin(); It != m_MyList.end();)    
{  
 list<CStudent*>::iterator It2;  
 It2 = It;  
 It++;  
  m_MyList.erase(It2);    
}  
注意,for循环里没有It++。

注意点二、当List成员在堆上创建时

先看下面一段代码:

list<CStudent *>myList;  
CStudent * pStudentA = new CStudent;  
  
StudentA->SetInfo(27, "StudentA");  
  
CStudent * pStudentB = new CStudent;  
  
StudentB->SetInfo(20, "StudentB");  

CStudent * pStudentC = new CStudent;  
  
StudentC->SetInfo(24, "StudentC");  

myList.push_back(pStudentA);  
  
myList.push_back(pStudentB);  
  
myList.push_back(pStudentC);  

list<CStudent*>::iterator It;  
for(It = myList.begin(); It != myList.end(); )  
{  
list<CStudent*>::iterator It2;  
It2 = It;  
It++;  
m_MyList.erase(It2);   
} 

    上面代码在堆上创建了三个对象,然后将三个对象加入队列,最后将对象erase,那还需不需要delete呢?答案是需要!erase不等同于delete,安全的做法是先delete再erase。

正确代码如下:

list<CStudent*>::iterator It;  
for(It = myList.begin(); It != myList.end(); )  
{  
list<CStudent*>::iterator It2;  
It2 = It;  
It++;  
Delete *It2;        // delete每个对象指针  
m_MyList.erase(It2);   
}  

     *It就是对象指针,不信的话可以打印出*It的值和StudentA的值比较一下,两个值相等。Delete *It2;一定要放在m_MyList.erase(It2);之前,因为m_MyList.erase(It2)执行之后节点被删除,*It2的值就被修改了。

<think>嗯,用户这次的问题非常聚焦,ta想深入理解Java集合框架中List、Set和Map的底层原理,特别是ArrayList和LinkedList的实现差异。从ta的提问方式看,应该是有一定Java基础的学习者或开发者,可能正在准备面试或优化代码性能。 用户之前已经接触过集合框架的基本概念(从引用内容看),现在需要更底层的实现细节。我注意到ta特别强调“底层原理”,说明不需要概念复述,而要深入到数据结构、内存管理和算法复杂度层面。ta还特意对比了ArrayList和LinkedList,这通常是面试高频考点,也可能是实际遇到了性能选择问题。 关于ArrayList: - 必须强调动态数组扩容机制,这是性能关键点。默认容量10和1.5倍扩容要具体说明 - 要解释为什么随机访问快(O(1))而插入删除慢(O(n)),结合数组内存连续特性 - System.arraycopy()的底层调用需要提及,这是ArrayList高效扩容的技术基础 关于LinkedList: - 需要说明双向链表节点结构(Node<E>的三字段) - 解释头尾指针优化对addFirst/addLast的性能提升 - 重点对比遍历性能:Iterator比for循环快得多(避免节点链式查找) List/Set/Map的对比维度要统一: 1. 数据结构本质(数组/链表/哈希表/红黑树) 2. 读写操作时间复杂度 3. 内存占用特点 4. 线程安全性(顺便提一句Collections.synchronizedList) 用户可能隐含的深层需求: - 如何根据场景选择集合类型?比如高并发读选CopyOnWriteArrayList - 为什么HashMap用红黑树不用AVL树?这个可以放在相关问题里 - ArrayList的缩容机制?实际开发中容易被忽略 需要警惕的易错点: - LinkedList的插入删除不一定快,在中间位置还是O(n) - HashSet就是HashMap的键,这个类比要讲透 - ConcurrentHashMap的分段锁原理虽然重要但可能超纲,简单提jdk8的CAS优化即可 最后生成的问题要覆盖: 1. 底层实现延伸(比如红黑树) 2. 线程安全方案 3. 实际应用场景选择 4. 避免教科书式提问,多用“什么情况下”的句式</think>以下是Java集合框架中List、Set、Map的底层原理详解,重点分析ArrayList与LinkedList的实现差异: --- ### **一、List接口实现原理** #### **1. ArrayList(动态数组实现)** - **数据结构**:基于**动态数组**(`Object[] elementData`) - **扩容机制**: - 初始容量10,添加元素超容量时按**1.5倍扩容**(`int newCapacity = oldCapacity + (oldCapacity >> 1)`) - 扩容时调用`Arrays.copyOf()`复制数据到新数组 - **时间复杂度**: - 随机访问:$O(1)$(直接通过索引定位) - 尾部插入:均摊$O(1)$ - 中间插入/删除:$O(n)$(需移动后续元素)[^1] - **内存占用**:连续内存空间,预留容量可能造成轻微浪费 #### **2. LinkedList(双向链表实现)** - **数据结构**:基于**双向链表**(`Node<E>`节点含`item, next, prev`) - **操作特点**: - 插入/删除:头尾操作$O(1)$,中间位置需先遍历定位$O(n)$ - 随机访问:$O(n)$(必须从头/尾遍历) - **内存占用**:每个元素额外存储两个指针(前驱/后继),内存开销更大[^2] ```java // LinkedList节点结构示例 private static class Node<E> { E item; Node<E> next; Node<E> prev; } ``` --- ### **二、Set接口实现原理** #### **1. HashSet(哈希表实现)** - **底层依赖**:基于`HashMap`实现(元素作为Key,Value为固定`Object`) - **去重机制**: - 依赖元素的`hashCode()`和`equals()`方法 - 哈希冲突时转为链表或红黑树(同HashMap)[^4] #### **2. TreeSet(红黑树实现)** - **底层结构**:基于`TreeMap`(红黑树) - **特性**:元素按自然顺序或Comparator排序,操作时间复杂度$O(\log n)$ --- ### **三、Map接口实现原理** #### **1. HashMap(数组+链表/红黑树)** - **数据结构**: - 数组`Node<K,V>[] table`存储桶(桶初始容量16) - 哈希冲突时,同一桶内转为链表(>8节点转红黑树) - **扩容机制**: - 负载因子0.75(默认),元素超阈值(容量*0.75)时扩容2倍 - 重新哈希所有元素(JDK8优化:高位异或保留哈希位)[^2] - **哈希计算**:`(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)` #### **2. LinkedHashMap(链表+哈希表)** - **扩展HashMap**:在Entry中添加`before/after`指针,维护插入顺序或访问顺序 --- ### **四、关键对比:ArrayList vs LinkedList** | **特性** | **ArrayList** | **LinkedList** | |------------------|---------------------|---------------------| | **底层结构** | 动态数组 | 双向链表 | | **随机访问** | $O(1)$(高效) | $O(n)$(低效) | | **头部插入** | $O(n)$(需移动元素)| $O(1)$(直接修改指针)| | **内存占用** | 连续内存,预留容量 | 分散内存,每个元素额外2指针 | | **适用场景** | 高频随机访问 | 高频头尾插入/删除 | > **示例场景**: > - 需要快速按索引读取数据 → **ArrayList** > - 频繁在列表中间插入/删除 → **LinkedList**(但需注意遍历定位成本)[^3] --- ### **五、相关问题** 1. **为什么HashMap链表长度超过8会转红黑树?** > 泊松分布统计表明,哈希冲突达8的概率极低($<10^{-6}$),转为树可保证最坏情况性能$O(\log n)$[^2]。 2. **ArrayList的`subList()`方法是否独立存储数据?** > 否!它返回原始列表的**视图**,修改`sublist`会影响原列表(共享底层数组)。 3. **如何选择List实现类?** | **操作需求** | **推荐实现** | |--------------------|---------------| | 读多写少,随机访问 | ArrayList | | 频繁头尾增删 | LinkedList | | 线程安全需求 | CopyOnWriteArrayList | 4. **HashSet如何保证元素唯一性?** > 添加元素时调用`HashMap.put(key, PRESENT)`,依赖Key的哈希值去重[^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值