集合一些易忽视的地方

1.请讲下Java里面的容器

集合Collection有两种:

List:元素是有序的,元素可以重复,因为该集合体有索引

   ArrayList:

    底层数据结构是数组,查询快,增删慢。

    线程不安全,效率高。

     当元素放满了后,以原长度的50%+1的长度加长集合容器的长度。

  Vector:

    底层数据结构是数组,查询快,增删慢。

    线程安全,效率低。

     当元素放满了后,以原长度100%的长度加长集合容器的长度。

  LinkedList:

    底层数据结构是链表,查询慢,增删快。

    线程不安全,效率高。

  Vector(线程安全的)相对ArrayList查询慢

  Vector相对LinkedList增删慢(数组结构)

Set:元素是无序的,元素不可以重复。

    a)、HashSet:不能保证元素的排列顺序,线程不同步。

    b)、TreeSet:可以set集合中的元素进行排序,线程不同步。

集合Map:

Map:存储键值对

    a)、HashMap:底层是哈希表数据结构,可以存入null作为键或值,线程不同步

    b)、HashTable:底层是哈希表数据结构,不可以存入null作为键或值,线程同步。

    c)、TreeMap:底层是二叉树结构,线程不同步。

2、fail-fast与fail-safe有什么区别?

  Iterator的fail-fast属性与当前的集合共同起作用,因此它不会受到集合中任何改动的影响。Java.util包中的所有集合类都被设计为fail-fast的,而java.util.concurrent中的集合类都为fail-safe的。Fail-fast迭代器抛出ConcurrentModificationException,而fail-safe迭代器从不抛出ConcurrentModificationException。

2.遍历一个List<String> strList = new ArrayList<>();有哪些不同的方式?哪种方式更安全?

//使用for-each循环

for(String obj : strList){

  System.out.println(obj);

}

//using iterator

Iterator<String> it = strList.iterator();

while(it.hasNext()){

  String obj = it.next();

  System.out.println(obj);

}

  使用迭代器更加线程安全,因为Iterator的fail-fast属性与当前的集合共同起作用,因此它不会受到集合中任何改动的影响。它可以确保,在当前遍历的集合元素被更改的时候,它会抛出ConcurrentModificationException。在遍历一个集合的时候,我们可以使用并发集合类来避免ConcurrentModificationException,比如使用CopyOnWriteArrayList,而不是ArrayList。

3.下面的这段代码有错吗?说说看?

List<String> list = new ArrayList<String>(2);

list.add("guan");

list.add("bao");

String[] array = new String[list.size()];

array = list.toArray();

解:直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它类型数组将出现 ClassCastException 错误。

使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一样的数组,大小就是 list.size()。

说明:使用 toArray 带参方法,入参分配的数组空间不够大时,toArray 方法内部将重新分配内存空间,并返回新数组地址;如果数组元素大于实际所需,下标为[ list.size() ]的数组元素将被置为 null,其它数组元素保持原值,因此最好将方法入参数组大小定义与集合元素个数一致。

4、看下面这段代码:

String[] str = new String[] { "you", "wo" };

List list = Arrays.asList(str);

1)如果在上述代码后添加如下代码会如何?

  list.add("yangguanbao");

2)如果添加如下代码:输出结果是?

  str[0] = "bunfly";

   System.out.println(list.get(0));

解:使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。

asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组。

  str[0] 改变,list.get(0)会随之修改。

5、Iterator是什么?

  Iterator接口提供遍历任何Collection的接口。我们可以从一个Collection中使用迭代器方法来获取迭代器实例。迭代器取代了Java集合框架中的Enumeration。迭代器允许调用者在迭代过程中移除元素。

6、Iterater和ListIterator之间有什么区别?

  (1)我们可以使用Iterator来遍历Set和List集合,而ListIterator只能遍历List。

  (2)Iterator只可以向前遍历,而LIstIterator可以双向遍历。

  (3)ListIterator从Iterator接口继承,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。

7、下面是一个集合的遍历的片段:

 1 List<String> list = new ArrayList<String>();
 2
 3 list.add("1");
 4
 5 list.add("2");
 6
 7 for (String item : list) {
 8
 9     if ("1".equals(item)) {
10
11         list.remove(item);
12
13     }
14
15 }      

1)请问上述操作如何?

2)若把list.remove(item)换成list.add(“3”);操作如何?

3)若在第6行添加list.add("3");那么代码会出错吗?

4)若把if语句中的“1”换成“2”,结果你感到意外吗?

运行结果:1)没错,后面3个都会报ConcurrentModificationException的异常;

  不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。

二者本质是一样的,都是通过Iterator迭代器来实现的遍历,foreach是增强版的for循环,可以看作是方式二的简化形式

正例:

 1 Iterator<String> iterator = list.iterator();
 2
 3 while (iterator.hasNext()) { //方式二
 4
 5 String item = iterator.next();
 6
 7     if (删除元素的条件) {
 8
 9         iterator.remove();
10
11     }
12
13 }

此小题更多疑问参考:http://xxgblog.com/2016/04/02/traverse-list-thread-safe/

首先,这涉及多线程操作,Iterator是不支持多线程操作的,List类会在内部维护一个modCount的变量,用来记录修改次数

举例:ArrayList源码

protected transient int modCount = 0;

每生成一个Iterator,Iterator就会记录该modCount,每次调用next()方法就会将该记录与外部类List的modCount进行对比,发现不相等就会抛出多线程编辑异常。

为什么这么做呢?我的理解是你创建了一个迭代器,该迭代器和要遍历的集合的内容是紧耦合的,意思就是这个迭代器对应的集合内容就是当前的内容,我肯定不会希望在我冒泡排序的时候,还有线程在向我的集合里插入数据对吧?所以Java用了这种简单的处理机制来禁止遍历时修改集合。

至于为什么删除“1”就可以呢,原因在于foreach和迭代器的hasNext()方法,foreach这个语法,实际上就是

while(itr.hasNext()){
    itr.next()
}

所以每次循环都会先执行hasNext(),那么看看ArrayList的hasNext()是怎么写的:

public boolean hasNext() {
    return cursor != size;
}

cursor是用于标记迭代器位置的变量,该变量由0开始,每次调用next执行+1操作,于是:

  你的代码在执行删除“1”后,size=1,cursor=1,此时hasNext()返回false,结束循环,因此你的迭代器并没有调用next查找第二个元素,也就无从检测modCount了,因此也不会出现多线程修改异常;但当你删除“2”时,迭代器调用了两次next,此时size=1,cursor=2,hasNext()返回true,于是迭代器傻乎乎的就又去调用了一次next(),因此也引发了modCount不相等,抛出多线程修改的异常。

当你的集合有三个元素的时候,你就会神奇的发现,删除“1”是会抛出异常的,但删除“2”就没有问题了,究其原因,和上面的程序执行顺序是一致的。

时间: 2024-10-17 10:32:54

集合一些易忽视的地方的相关文章

Javascript中容易被忽视的地方

(1) alert(undefined==null);返回的结果是true (2)Javascript中的类型 http://www.cnblogs.com/winter-cn/archive/2009/12/07/1618281.html JavaScript中的类型 JavaScript标准中规定了9种类型:Undefined Null Boolean String Number Object Reference List Completion 其中,Reference List Compl

平时容易忽视的地方之一:java在抽取方法时,什么时候该用void

当一个类中多个方法有相同编码,或该部分编码可以作为一个整体,适合抽取出一个方法时,要注意这个抽取的方法的返回值,什么时候可以用void,什么时候不能用void? 先看代码: import lombok.Data; import org.junit.Test; public class MyTest { @Test public void test(){ Student student = new Student(); student.setName("Jacky"); int age

闲谈之在大城市为生活奔波的小角色和去更易生活的地方之间该如何抉择和度量

以下为几个好友的闲谈,由于这个问题我们中有同学即将面对,也有已经经历过的,因此我们颇有感触.总的来说,无论哪种选择都没对没错,我比较喜欢一句话,成年人的世界里没有对错.你想过什么生活,你就走那条路,选择了就不要后悔.上天为你开了两扇门,你进了一扇门,另一扇也就关闭了:上天给你关了一扇门,同时他也给你开了另一扇门.

ThinkPHP的易忽视点小结

1.使用对象的方法插入数据 D用法. $Form = D('Form'); $data['title'] = 'ThinkPHP'; $data['content'] = '表单内容'; $Form->add($data); 其实thinkphp还支持对象的方式直接向数据库插入数据,如下: $Form = D('Form'); $Form->title = 'ThinkPHP'; $Form->content = '表单内容'; $Form->add(); 2.不指定条件对数据更新

PMP杂谈--PMP中一些容易忽视的地方

识别干系人:这个过程是持续的,在整个项目的生命周期中都要持续识别干系人. 组织过程资产和事业环境因素:这两个东西在过程的输入中似乎经常看到,但有时候又看不到,不要纠结了 ,不要浪费脑细胞去背诵哪个有,哪个没有了,其实这两个在所有的过程中都要予以考虑的.即便没有被明确的列入输入.(这是在第五版 PMBOK第三章 项目管理过程章节 47页 第五行 明确说明的,可找到)  版权声明:本文为博主原创文章,未经博主允许不得转载.

php容易忽视的地方

一:bool in_array ( mixed $needle , array $haystack [, bool $strict ] ) 用的时候加最后一个参数,判断类型 <?php $a = array('1.10', 12.4, 1.13); if (in_array('12.4', $a, true)) { echo "'12.4' found with strict check\n"; } if (in_array(1.13, $a, true)) { echo &qu

OpenCV里IplImage和char *的相互转换,以及极易忽视的细节

OpenCV中IplImage和单字节char*的相互转换 从 IplImage到 char* : data = image->imageData //对齐的图像数据 或者data = image->imageDataOrigin //未对齐的原始图像数据 从 char* 到 IplImage: image =cvCreateImageHeader(cvSize(width,height), depth, channels); cvSetData(image, data, step); ste

[转]ThinkPHP的CURD易忽视点小结

转自: http://www.oschina.net/code/snippet_2285640_44437. 1.使用对象的方法插入数据 D用法. $Form = D('Form'); $data['title'] = 'ThinkPHP'; $data['content'] = '表单内容'; $Form->add($data); 其实thinkphp还支持对象的方式直接向数据库插入数据,如下: $Form = D('Form'); $Form->title = 'ThinkPHP'; $F

机电传动控制学习笔记3

首先写写本周的仿真作业.仿真结果:启动后约在3.9至4.2秒转速会达到稳定,稳定值在1213r/min左右(不能达到1220r/min,因为一直会有0.6欧姆的内阻存在),而上升曲线只有些许波澜. 仿真要求:电机启动仿真要求结合要本周学习的直流电机机械特性,用Modelica设计和仿真一个直流电机串电阻启动过程,电机工作在额定电压和额定磁通下,采用串三段或四段电阻启动,整个启动过程电枢电流中不能超过额定电流的3倍.额定电压240V,额定电流16.2A,额转矩29.2N.m,额定转速1220 r/