一、并发修改异常
对集合进行遍历时,对集合进行增、删操作,会引起并发修改异常。
方案一:使用迭代器
foreach底层也是使用Iterator迭代器进行迭代的
public static void main(String[] args) {
ArrayList<Integer> arrayLists = new ArrayList<>();
arrayLists.add(new Integer(1));
arrayLists.add(new Integer(2));
arrayLists.add(new Integer(3));
arrayLists.add(new Integer(4));
arrayLists.add(new Integer(5));
arrayLists.add(new Integer(6));
arrayLists.add(new Integer(7));
arrayLists.add(new Integer(8));
Iterator<Integer> iterator = arrayLists.iterator();
while(iterator.hasNext()){
Integer num = iterator.next();
if(num.equals(new Integer(3))){
iterator.remove(); //删除操作
}
}
}
方案二:扩容处理
需要remove/ add 的项集存入到另一集合中,在集合遍历结束后,再删除/添加 这些项集。
二、迭代器的介绍
迭代器是一个对象,他的工作是遍历并选择序列中的对象。此外,迭代器通常被称为轻量级对象, 正因为它的创建代价小。Java中的Iterator只能单向移动,这个Iterator只能用来
- 使用方法iterator()要求容器放回一个Iterator,Iterator将准备好返回序列的第一个元素。
- 使用next()获得序列中的下一个元素。
- 使用hasNext()检查序列中是否还有元素(通常作为判断条件)。
- 使用remove()将迭代器新近返回的元素删除
这里要注意一点:迭代器并不是独立的一个对象,事实上它是操作容器的工具,通过迭代器的方法(比如remove)是直接影响到原容器的。
三、迭代器的介绍
-
为何迭代器的代价比较小?
因为迭代器不是一次性产生所有元素,下一个元素的创建要经过一次迭代,也就是调用一次next函数。 -
容器可以通过调用iterator函数来创建一个对应类型的iterator对象,换句话说iterator对象可以接受任何容器调用iterator函数对它的初始化。这也就解释了为何迭代器可以同一不同类型容器的代码重用问题。
原文链接:https://blog.csdn.net/ztlzlzl/article/details/106987229
https://blog.csdn.net/qq_37935670/article/details/79583339
四、算法中的实际应用
函数功能:在候选项集中找到结果集
【版本1】利用2个迭代器进行遍历 (并发修改异常)
//【版本1】利用2个迭代器进行遍历 (并发修改异常)
//第一层循环,iterator遍历 x
Iterator<ArrayList<Integer>> iterator = s_psfui.keySet().iterator();
while(iterator.hasNext()){
ArrayList<Integer> x = iterator.next();
ULStructure x_list = s_psfui.get(x);
//第二层循环,iterator遍历 y
Iterator<ArrayList<Integer>> iterator2 = s_psfui.keySet().iterator();
while(iterator2.hasNext()){
ArrayList<Integer> y = iterator2.next();
ULStructure y_list = s_psfui.get(y);
if((x_list.size() >= y_list.size() && x_list.utilSum() > y_list.utilSum()) || (x_list.size() > y_list.size() && x_list.utilSum() >= y_list.utilSum())){
iterator2.remove(); //删除y
}
}
}
【版本2】for + 迭代器进行遍历 (报并发修改异常)
//【版本2】for + 迭代器进行遍历 (报并发修改异常)
//第一层循环,iterator遍历 x
Iterator<ArrayList<Integer>> iterator = s_psfui.keySet().iterator();
while(iterator.hasNext()){
ArrayList<Integer> x = iterator.next();
ULStructure x_list = s_psfui.get(x);
//删除的项集
ArrayList<ArrayList<Integer>> remove_set = new ArrayList<>();
//第二层循环,iterator遍历 y
for(ArrayList<Integer> y : s_psfui.keySet()){
ULStructure y_list = s_psfui.get(y);
if((x_list.size() >= y_list.size() && x_list.utilSum() > y_list.utilSum()) || (x_list.size() > y_list.size() && x_list.utilSum() >= y_list.utilSum())){
remove_set.add(y); //删除y
}
}
for(ArrayList<Integer> itemset : remove_set){
s_psfui.remove(itemset);
}
}
即使不在for()循环中做删除,也不行!
【小测试】 iterator1 & iterator2 两个迭代器是否相互影响
//【小测试】 iterator1 & iterator2 两个迭代器是否相互影响(iterator2修改后,iterator1是否知道增删。回答:不知道)
Iterator<ArrayList<Integer>> iterator1 = s_psfui.keySet().iterator();
Iterator<ArrayList<Integer>> iterator2 = s_psfui.keySet().iterator();
iterator2.next();
if(iterator2.hasNext())
iterator2.remove();
System.out.println(iterator1.hasNext());//true
iterator1.next(); //实际运行结果,这里抛出并发修改异常,说明1的信息与2不同步(iterator2.remove()后,iterator1 找不到下一个元素)
System.out.println(iterator1.hasNext());//false--》如果联动,那么二号元素已经被删除,输出false
//最终结论:iterator只能删除迭代器当前指向的元素,否则可能会抛出并发修改异常
【版本3】两层for()循环,将删除元素放到集合中,遍历结束后再删除
//【版本3】两层for()循环,将删除元素放到集合中,遍历结束后再删除
//第一层循环,iterator遍历 x
Set<ArrayList<Integer>> remove_set=new HashSet<>();
for(ArrayList<Integer> x : s_psfui.keySet()) {
if (!remove_set.contains(x)) {
ULStructure x_list = s_psfui.get(x);
//第二层循环,iterator遍历 y
for (ArrayList<Integer> y : s_psfui.keySet()) {
if(!remove_set.contains(y)) {
ULStructure y_list = s_psfui.get(y);
if ((x_list.size() >= y_list.size() && x_list.utilSum() > y_list.utilSum()) || (x_list.size() > y_list.size() && x_list.utilSum() >= y_list.utilSum())) {
remove_set.add(y); //将需要删除元素加入到集合中
}
}
}
}
}
//删除被支配的项集
for (ArrayList<Integer> integerArrayList : remove_set) {
s_psfui.remove(integerArrayList);
}
remove_set.clear();
几个改进的点:
版本3需要一个额外的空间remove_set用于存储待删除的项集,但remove_set最大空间占用只是跟原集合s_psfui一样,是一个2倍的空间开销。扩容实现需要较小的内存开销,在线性范围内,可以接受。(并且避免了使用迭代器,每次用迭代器容易出奇奇怪怪的问题)
1. 在remove_set使用完毕后,进行remove_set.clear(),及时释放内存。
2. 外层可以用迭代器进行遍历,当判断到remove_set.contains(x),可以顺势将X在迭代器中进行删除。(不知道是否可行,可以试试)
3. 需要注意for()循环层中,需要进行remove_set.contains()的操作。