• 集合一些易忽视的地方


    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”就没有问题了,究其原因,和上面的程序执行顺序是一致的。

     

     

     

  • 相关阅读:
    图解SSH隧道功能
    su和sudo命令的用法
    Linux下下载百度网盘资料
    WPS Office 2019 for Linux来了
    Linux下的录屏软件Kazam
    用hdparm获取硬盘参数
    百度,你能不能有点节操?
    locate的基本用法
    Java class不分32位和64位
    Web站点抓取工具webhttrack
  • 原文地址:https://www.cnblogs.com/mmzs/p/7765523.html
Copyright © 2020-2023  润新知