科技改变生活 · 科技引领未来

  • 首页
  • 资讯
  • 技术
  • 百科
  • 问答
  • 学习
  • 看看
  • 站长
  • 生活
  • 快讯

首页 > 学习 > 科技政策

add一e多少钱(面试)

时间:2022-09-27 04:04 作者:李书远

1、foreach遍历ArrayList过程中使用add和remove我们先来看看使用foreach遍历ArrayList过程中使用add和remove会出现什么样子的结果,然后再分析一下。publicstaticvoidmain(Stri

1、foreach遍历ArrayList过程中使用 add 和 remove

我们先来看看使用foreach遍历ArrayList过程中使用 add 和 remove 会出现什么样子的结果,然后再分析一下。

public static void main(String[] args) {     List list = new ArrayList<>();     for (int i = 0; i < 20; i++) {         list.add(i);     }     for (Integer j : list) {         if (j.equals(3)) {             list.remove(3);         }         System.out.println(j);     } }

运行结果:

0 1 2 3 Exception in thread "main" java.util.ConcurrentModificationException 	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911) 	at java.util.ArrayList$Itr.next(ArrayList.java:861) 	at test.Test.main(Test.java:12)

结果是出现了ConcurrentModificationException 异常,追踪下抛出异常的位置(ArrayList.java:911)

final void checkForComodification() {     if (modCount != expectedModCount)         throw new ConcurrentModificationException(); }

结果是出现了ConcurrentModificationException 异常,追踪下抛出异常的位置(ArrayList.java:911)

final void checkForComodification() {     if (modCount != expectedModCount)         throw new ConcurrentModificationException(); }

这个地方告诉我们如果 modCount 不等于 expectedModCount 的时候,就会抛出这个异常信息,那么这两个参数都代表了什么东西呢?为什么不相等的时候,就会出现异常呢?

2、追根溯源

2.1、modCount是什么?

这时候就要让我们去看源码了,在我们点到这个变量的时候,就会有注释告诉我们了 modCount 是 AbstractList 类中的一个成员变量,该值表示对List的修改次数。

这时候我们来看看 remove 方法中是否对这个变量进行了增减。

add一e多少钱(面试)

大家可以看到,在 remove 的方法中,实际上只是对 modCount 进行了++,那 expectedModCount 又是个什么东西呢?

2.2、expectedModCount 是什么?

expectedModCount 是 ArrayList 中的一个内部类——Itr中的成员变量,我们来看下怎么又扯出个内部类Itr。

通过反编译可以发现foreach编译后内部是使用迭代器实现的。

迭代器是通过list.iterator()实例化的,list.iterator()就返回了一个内部类Itr的对象,从源码中可以看到Itr实现了Iterator接口,同时声明了expectedModCount这个成员变量, expectedModCount 表示对ArrayList修改次数的期望值,它的初始值为 modCount。

2.3、熟悉的checkForComodification方法

从源码可以看到这个类的next和remove方法里面都调用了一个checkForComodification方法,看到checkForComodification是不是很熟悉,这不就是异常的抛出位置吗。

checkForComodification方法是通过判断modCount和expectedModCount是否相等来决定是否抛出并发修改异常。

2.4、流程回顾

通过查看编译后的class文件,可以看出大致流程如下:当j为3时,调用了remove方法,remove方法中修改了modCount值,然后再输出j值,再进入下一次循环,此时hasNext为true,进入循环体第一行代码,调用next方法,next方法再调用checkForComodification方法,然后发现expectedModCount和modCount不一致,最终抛出ConcurrentModificationException 异常。

也就是说,expectedModCount 初始化为 modCount 了,但是后面 expectedModCount 没有修改,而在 remove 和 add 的过程中修改了modCount ,这就导致了执行的时候,通过 checkForComodification 方法来判断两个值是否相等,如果相等了,那么没问题,如果不相等,那就给你抛出一个异常来。

而这也就是我们通俗说起来的 fail-fast 机制,也就是快速检测失败机制。

3、避免fail-fast 机制

3.1、使用listIterator或iterator

fail-fast 机制也是可以避免的,比如再拿出来我们上面的代码

public static void main(String[] args) {     List list = new ArrayList<>();     for (int i = 0; i < 5; i++) {         list.add(i);     }       System.out.println("没有删除元素前"+list.toString());     // 迭代器使用listIterator和iterator均可     ListIterator listIterator = list.listIterator();     while(listIterator.hasNext()){         Integer integer = listIterator.next();         if(integer==3){             listIterator.remove();             listIterator.add(9);         }     }     System.out.println("删除元素后"+list.toString()); }

这样的话,你就发现是可以运行的,也是没有问题的,我们看运行结果:

没有删除元素前[0, 1, 2, 3, 4] 删除元素后[0, 1, 2, 9, 4]

结果也是显而易见的,我们实现了在 foreach 中进行 add 和 remove 的操作.

这里有个注意点,迭代器使用listIterator和iterator均可,看源码可以知道 listIterator其实使用的ListItr内部类,ListItr是继承了Itr类的,同时自己封了一些方法,例如add,hasPrevious,previous等等。所以代码中的remove方法是Itr类的,add方法是ListItr类的

listIterator和iterator区别:

  1. 使用范围不同,Iterator可以应用于所有的集合,Set、List和Map和这些集合的子类型。而ListIterator只能用于List及其子类型。
  2. ListIterator有add方法,可以向List中添加对象,而Iterator不能。
  3. ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator不可以。
  4. ListIterator可以定位当前索引的位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
  5. 都可实现删除操作,但是ListIterator可以实现对象的修改,set()方法可以实现。Iterator仅能遍历,不能修改。

3.2、使用CopyOnWriteArrayList

CopyonWriteArrayList 这个类也是能解决 fail-fast 的问题的,我们来试一下:

public static void main(String[] args) {     CopyOnWriteArrayList list = new CopyOnWriteArrayList<>();     for (int i = 0; i < 5; i++) {         list.add(i);     }     System.out.println("没有删除元素前"+list.toString());     for (Integer integer : list) {         if(integer.equals(3)){             list.remove(3);             list.add(9);         }     }     System.out.println("删除元素后"+list.toString()); }

运行结果:

没有删除元素前[0, 1, 2, 3, 4] 删除元素后[0, 1, 2, 4, 9]

CopyOnWriteArrayList实现了对这个元素中间进行移除添加的操作,那么他的内部源码是怎么实现的,实际上很简单,复制

也就是他创建一个新的数组,再将旧的数组复制到新的数组上,但是为什么很少有人推荐这种做法,根本原因还是 复制

因为你使用了复制,那么就一定会出现有两个存储相同内容的空间,这样消耗了空间,最后进行 GC 的时候,那是不是也需要一些时间去清理他,所以个人不是很推荐,但是写出来的必要还是有的。

3.2.1、CopyOnWriteArrayList的add方法

public boolean add(E e) {     // 可重入锁     final ReentrantLock lock = this.lock;     // 获取锁     lock.lock();     try {         // 元素数组         Object[] elements = getArray();         // 数组长度         int len = elements.length;         // 复制数组         Object[] newElements = Arrays.copyOf(elements, len + 1);         // 存放元素e         newElements[len] = e;         // 设置数组         setArray(newElements);         return true;     } finally {         // 释放锁         lock.unlock();     } }

处理流程如下:

获取锁(保证多线程的安全访问),获取当前的Object数组,获取Object数组的长度为length,进入步骤②。

根据Object数组复制一个长度为length+1的Object数组为newElements(此时,newElements[length]为null),进入下一步骤。

将下标为length的数组元素newElements[length]设置为元素e,再设置当前Object[]为newElements,释放锁,返回。这样就完成了元素的添加。

3.2.2、CopyOnWriteArrayList的remove方法

public E remove(int index) {     // 可重入锁     final ReentrantLock lock = this.lock;     // 获取锁     lock.lock();     try {         // 获取数组         Object[] elements = getArray();         // 数组长度         int len = elements.length;         // 获取旧值         E oldValue = get(elements, index);         // 需要移动的元素个数         int numMoved = len - index - 1;         if (numMoved == 0) // 移动个数为0             // 复制后设置数组             setArray(Arrays.copyOf(elements, len - 1));         else { // 移动个数不为0             // 新生数组             Object[] newElements = new Object[len - 1];             // 复制index索引之前的元素             System.arraycopy(elements, 0, newElements, 0, index);             // 复制index索引之后的元素             System.arraycopy(elements, index + 1, newElements, index,                                 numMoved);             // 设置索引             setArray(newElements);         }         // 返回旧值         return oldValue;     } finally {         // 释放锁         lock.unlock();     } }

处理流程如下:

获取锁,获取数组elements,数组长度为length,获取索引的值elements[index],计算需要移动的元素个数(length - index - 1),若个数为0,则表示移除的是数组的最后一个元素,复制elements数组,复制长度为length-1,然后设置数组,进入步骤③;否则,进入步骤②

先复制index索引前的元素,再复制index索引后的元素,然后设置数组。

释放锁,返回旧值。

注意

CopyOnWriteArrayList解决 fail-fast 的问题不是通过迭代器来remove或add元素的,而是通过list本身的remove和add方法,所以add的元素位置也不一样,迭代器是当前位置后面一个,CopyOnWriteArrayList是直接放到最后。

有想法的同学可以看看CopyOnWriteArrayList的listIterator和iterator,其实是一样的,都是返回的COWIterator内部类。

在COWIterator内部类中是不支持remove、set、add操作的,至少我使用的jdk1.8是不支持,会直接抛出UnsupportedOperationException异常:

先写到这儿,后面有空再补充。

相关话题

  • 农村医疗保险要交多少钱(2023年新农合即将开缴)
  • 上汽大众途岳多少钱(途岳全系热销中)
  • 雄性激素检查要多少钱(脱发别走进雄性激素的)
  • 上海金桥房价多少钱一平(金桥重点学校旁)
  • 迪拜房价多少钱一平(第二个迪拜)
  • 橱柜不锈钢台面多少钱一米(我就想厨房台面装不锈钢)
  • 拔牙大概要多少钱一颗(有人拔牙收100块)
  • 回收旧电瓶多少钱一斤(一组旧电池卖多少不吃亏)
  • 打包盒生产设备多少钱(628期)
  • 回收旧电瓶多少钱一斤(最新1月12日全国重点废电瓶企业参考价格行情分析)
  • 新西兰旅游签证要多少钱(7月起)
  • 宝马4s店保养一次多少钱(除了免费洗车免费午餐)
  • 橱柜不锈钢台面多少钱一米(900元)
  • 高中升大专需要多少钱(高中学历)
  • 河南安阳房价多少钱一平方(一图一表看懂河南省安阳市12月份各区县房价涨跌情况)
  • 皮秒激光一般多少钱(轻医美界的)
  • 自体脂肪丰胸手术一般多少钱啊(自体脂肪丰胸怎么样)
  • 回春草价格多少钱一斤(深山老林中的)
  • 荣事达养生壶多少钱一个(9款养生壶口碑对比)
  • 女性检查生育能力项目多少钱(超三成已生育女性会出现漏尿)

热门推荐

  • 《江西省科技创新促进条例》计划明年1月1日起实行!
  • 古钟二锅头46度多少钱(白酒板块将添新成员)
  • 装wifi多少钱一年(香港办宽带怎么这么贵)
  • 做饭保姆一个月多少钱(55岁保姆哭诉)
  • 咸阳到西安打车多少钱(西安咸阳机场停车费多少钱一天)
  • 荣威550多少钱一台(荣威550插电式混动版月用车成本为1929)
  • 一克拉钻石多少钱大概(预算5)
  • 西凤酒15年多少钱一瓶(西凤酒全面提价)
  • 液晶显示屏多少钱一块(1099元的165Hz显示器)
  • 二手苹果x能卖多少钱(二手宝马X1怎么买)
  • 插座维修上门多少钱(这是典型的贪小便宜吃大亏)
  • 刷脸支付加盟费多少钱(5万代理费)
  • 面包机多少钱一台家用(Panasonic)
  • 23年工龄退休拿多少钱(工龄21年)
  • 二手木地板多少钱一平方(买二手实木地板靠谱吗)
  • 在银行工作工资多少钱一个月(26岁银行转岗做出纳)
  • 葛陶中紫砂壶市场价多少钱(做壶)
  • 卤江南加盟费是多少钱(食品饮料行业之连锁行业研究)
  • 奔富707多少钱一瓶(葡萄酒界的低调天王奔富)
  • 信阳脱毛多少钱(多地兴建家禽屠宰场)

李书远

关注
免责声明:本文章由会员“李书远”发布,如果文章侵权,请联系我们处理,本站仅提供信息存储空间服务 如因作品内容、版权和其他问题请于本站联系

关注排行榜

  1. 1《江西省科技创新促进条例》计划明年1月1日起实行!
  2. 2活牛多少钱一斤(牛价格多少钱一头)
  3. 3铝单板多少钱一平方(福建铝单板的厂家哪家质量好)
  4. 4一箱牛奶多少钱(是不是我这种妈妈才会给孩子买29一箱的临期纯牛奶)
  5. 5普洱茶一般多少钱(一克就有五亿霉菌)
  6. 6gtx1060多少钱(幽灵线)
  7. 7一辆电动车多少钱(2500元左右电动车)
  8. 8苹果6换主板多少钱(花钱花在刀刃上)
  9. 9养老保险每年多少钱(养老保险每年缴费8000到1万元)
  10. 10代缴社保一个月多少钱(社保缴费每月2600)

编辑精选

Copyright ©2009-2022 KeJiTian.Com, All Rights Reserved

版权所有 未经许可不得转载

增值电信业务经营许可证备案号:辽ICP备14006349号

网站介绍 商务合作 免责声明 - html - txt - xml