欢迎来到我的博客小站。  交流请加我微信好友: studyjava。  也欢迎关注公众号:Java学习之道 Fork me on GitHub

集合一些易忽视的地方

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:底层是二叉树结构,线程不同步。

 

2fail-fastfail-safe有什么区别?

  Iteratorfail-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] = "mmzs";

    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、IteraterListIterator之间有什么区别?

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

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

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

 

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循环,可以看作是方式二的简化形式

正例:Java学习交流QQ群:603654340 我们一起学Java!

 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 }

首先,这涉及多线程操作,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”就没有问题了,究其原因,和上面的程序执行顺序是一致的。

 

 

 

 

posted @ 2017-11-01 17:34  淼淼之森  阅读(481)  评论(0编辑  收藏  举报
  👉转载请注明出处和署名