zoukankan      html  css  js  c++  java
  • 集合框架_DAY15

    1:集合(掌握)
      (1)集合的由来
      我们需要对多个对象进行存储和获取。可以使用对象数组。但是,如果对象的个数是变化的,对象数组就解决不了了。怎么办呢?java就提供了集合类解决。
      (2)集合和数组的区别?
        A:长度问题
          数组长度固定,集合长度可变
        B:存储元素类型1
          数组只能存储同一种类型。集合可以存储不同类型的对象
        C:存储元素类型2
          数组可以存储基本类型,也可以存储引用类型。集合只能存储引用类型。
      (3)集合体系的由来:
        多种集合的数据结构不同,但是,它们有共性的功能。通过不断的向上抽取,最终形成了集合的体系结构。

                                                      Collection
                                                               |--List
                                                                       |--ArrayList
                                                                       |--Vector
                                                                       |--LinkedList
                                                               |--Set
                                                                       |--HashSet
                                                                       |--TreeSet
      (4)Collection接口的功能:
        A:添加功能
          add(Object obj)
        B:删除功能
          remove(Object obj)
        C:判断功能
          contains(Object obj)
        D:获取功能
          Iterator iterator()
        E:长度功能
          size()
      (5)迭代器
        A:迭代器其实就是遍历集合的一种方式。
        B:迭代器的使用:
          迭代器不能单独使用,它依赖于集合而存在。
        C:使用步骤
          a:通过集合对象调用iterator()方法得到迭代器对象。
          b:通过迭代器对象的hasNext()方法判断是否有元素。
          c:通过迭代器对象的next()获取元素。
        D:原理:
          是以内部类形式存在的。
      (6)案例:(掌握)
        集合的使用步骤:
          A:创建集合对象
          B:创建元素对象
          C:把元素添加到集合中
          D:遍历集合
             a:通过集合对象调用iterator()方法得到迭代器对象。
             b:通过迭代器对象的hasNext()方法判断是否有元素。
             c:通过迭代器对象的next()获取元素。

          存储字符串并遍历:

              Collection c = new ArrayList();

              String s1 = "hello";
              String s2 = "world";
              String s3 = "java";

              c.add(s1);
              c.add(s2);
              c.add(s3);

              Iterator it = c.iterator();
              while(it.hasNext()) {
                String s = (String) it.next();
                 System.out.println(s);
              }

        存储自定义对象并遍历:自己补齐。

    2:List及其子类(掌握)
      (1)List的特点:
         Collection
            |--List:元素有序(存入顺序和取出顺序一致),可重复。
            |--Set:元素无序,唯一。
      (2)List的特有功能:
        A:添加功能
          add(int index,Object obj)
        B:删除功能
          remove(int index)
        C:获取功能
          get(int index)
        D:修改功能
          set(int index,Object obj)
      (3)案例:
        List存储字符串并遍历。
        List存储自定义对象并遍历。


        使用集合存储自定义对象的步骤:
          1、定义要存储到集合当中的类
          2、创建 集合 集合引用变量 = new 集合();
          3、创建要存储到集合当中的类的对象们
          4、调用集合方法,存储对应的对象
          5、返回对应集合的迭代器
          6、使用迭代器判断是否有下个元素
          7、如果有下个元素则获取下个元素

      (4)ListIterator(理解)

        A:可以逆向遍历,但是要先正向遍历,所以一般不用。
        B:可以解决并发修改异常问题。
        并发修改异常:在用迭代器遍历集合的时候,通过集合去修改了集合的元素。
        解决方案:
          a:通过列表迭代器遍历,通过列表迭代器修改集合。
          b:通过集合遍历,通过集合修改集合。

    3、集合遍历方式

    集合类的通用遍历方式, 用迭代器迭代:

          Iterator it = list.iterator();
          while(it.hasNext()) {
            Object obj = it.next();
          }
     
    Map遍历方式:
     
    (1)通过获取所有的key按照key来遍历
    //Set<Integer> set = map.keySet(); //得到所有key的集合
    for (Integer in : map.keySet()) {
        String str = map.get(in);//得到每个key多对用value的值
    }
     
    (2)通过Map.entrySet使用iterator遍历key和value
    Iterator<Map.Entry<Integer, String>> it = map.entrySet().iterator();
    while (it.hasNext()) {
         Map.Entry<Integer, String> entry = it.next();
           System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
    }
     
    (3)通过Map.entrySet遍历key和value,推荐,尤其是容量大时
    for (Map.Entry<Integer, String> entry : map.entrySet()) {
        //Map.entry<Integer,String> 映射项(键-值对)  有几个方法:用上面的名字entry
        //entry.getKey() ;entry.getValue(); entry.setValue();
        //map.entrySet()  返回此映射中包含的映射关系的 Set视图。
        System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
    }
     
    (4)通过Map.values()遍历所有的value,但不能遍历key
    for (String v : map.values()) {
        System.out.println("value= " + v);
    }
     
    List遍历方式:
    (1):
    for(Iterator iterator = list.iterator();iterator.hasNext();){                    
        int i = (Integer) iterator.next();                   
        System.out.println(i);               
    }
    (2):
    Iterator iterator = list.iterator();
    while(iterator.hasNext()){
        int i = (Integer) iterator.next();
        System.out.println(i);
    }
    (3):
    for (Object object : list) { 
        System.out.println(object); 
    }
    (4):
    for(int i = 0 ;i<list.size();i++) {  
        int j= (Integer) list.get(i);
        System.out.println(j);  
    }
     
    数据元素是怎样在内存中存放的?
     主要有2种存储方式:
    (1)顺序存储,Random Access(Direct Access):
          这种方式,相邻的数据元素存放于相邻的内存地址中,整块内存地址是连续的。可以根据元素的位置直接计算出内存地址,直接进行读取。读取一个特定位置元素的平均时间复杂度为O(1)。正常来说,只有基于数组实现的集合,才有这种特性。Java中以ArrayList为代表。
    (2)链式存储,Sequential Access:
        这种方式,每一个数据元素,在内存中都不要求处于相邻的位置,每个数据元素包含它下一个元素的内存地址。不可以根据元素的位置直接计算出内存地址,只能按顺序读取元素。读取一个特定位置元素的平均时间复杂度为O(n)。主要以链表为代表。Java中以LinkedList为代表。
     
    每个遍历方法的实现原理是什么?
    (1)传统的for循环遍历,基于计数器的:
            遍历者自己在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后,停止。主要就是需要按元素的位置来读取元素。
    (2)迭代器遍历,Iterator:
            每一个具体实现的数据集合,一般都需要提供相应的Iterator。相比于传统for循环,Iterator取缔了显式的遍历计数器。所以基于顺序存储集合的Iterator可以直接按位置访问数据。而基于链式存储集合的Iterator,正常的实现,都是需要保存当前遍历的位置。然后根据当前位置来向前或者向后移动指针。
    (3)foreach循环遍历:
            根据反编译的字节码可以发现,foreach内部也是采用了Iterator的方式实现,只不过Java编译器帮我们生成了这些代码。
     
    各遍历方式对于不同的存储方式,性能如何?
    (1)传统的for循环遍历,基于计数器的:
            因为是基于元素的位置,按位置读取。所以我们可以知道,对于顺序存储,因为读取特定位置元素的平均时间复杂度是O(1),所以遍历整个集合的平均时间复杂度为O(n)。而对于链式存储,因为读取特定位置元素的平均时间复杂度是O(n),所以遍历整个集合的平均时间复杂度为O(n2)(n的平方)。
    ArrayList按位置读取的代码:直接按元素位置读取。
    复制代码
    transient Object[] elementData;
    
    public E get(int index) {
        rangeCheck(index);
        return elementData(index);
    }
    
    E elementData(int index) {
        return (E) elementData[index];
    }
    复制代码

    LinkedList按位置读取的代码:每次都需要从第0个元素开始向后读取。其实它内部也做了小小的优化。

    复制代码
    transient int size = 0;
    transient Node<E> first;
    transient Node<E> last;
    
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
    
    Node<E> node(int index) {
        if (index < (size >> 1)) {   //查询位置在链表前半部分,从链表头开始查找
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {                     //查询位置在链表后半部分,从链表尾开始查找
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
    复制代码
     (2)迭代器遍历,Iterator:
            那么对于RandomAccess类型的集合来说,没有太多意义,反而因为一些额外的操作,还会增加额外的运行时间。但是对于Sequential Access的集合来说,就有很重大的意义了,因为Iterator内部维护了当前遍历的位置,所以每次遍历,读取下一个位置并不需要从集合的第一个元素开始查找,只要把指针向后移一位就行了,这样一来,遍历整个集合的时间复杂度就降低为O(n);
    (这里只用LinkedList做例子)LinkedList的迭代器,内部实现,就是维护当前遍历的位置,然后操作指针移动就可以了:
    代码:
    复制代码
    public E next() {
        checkForComodification();
        if (!hasNext())
            throw new NoSuchElementException();
    
        lastReturned = next;
        next = next.next;
        nextIndex++;
        return lastReturned.item;
    }
    
    public E previous() {
        checkForComodification();
        if (!hasPrevious())
            throw new NoSuchElementException();
    
        lastReturned = next = (next == null) ? last : next.prev;
        nextIndex--;
        return lastReturned.item;
    }
    复制代码
     
    (3)foreach循环遍历:
            分析Java字节码可知,foreach内部实现原理,也是通过Iterator实现的,只不过这个Iterator是Java编译器帮我们生成的,所以我们不需要再手动去编写。但是因为每次都要做类型转换检查,所以花费的时间比Iterator略长。时间复杂度和Iterator一样。
    Iterator和foreach字节码如下:

    //使用Iterator的字节码:
    Code:
    0: new #16 // class java/util/ArrayList
    3: dup
    4: invokespecial #18 // Method java/util/ArrayList."<init>":()V
    7: astore_1
    8: aload_1
    9: invokeinterface #19, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
    14: astore_2
    15: goto 25
    18: aload_2
    19: invokeinterface #25, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
    24: pop
    25: aload_2
    26: invokeinterface #31, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
    31: ifne 18
    34: return


    //使用foreach的字节码:
    Code:
    0: new #16 // class java/util/ArrayList
    3: dup
    4: invokespecial #18 // Method java/util/ArrayList."<init>":()V
    7: astore_1
    8: aload_1
    9: invokeinterface #19, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
    14: astore_3
    15: goto 28
    18: aload_3
    19: invokeinterface #25, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
    24: checkcast #31 // class loop/Model
    27: astore_2
    28: aload_3
    29: invokeinterface #33, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
    34: ifne 18
    37: return

    各遍历方式的适用于什么场合?
    (1)传统的for循环遍历,基于计数器的:
            顺序存储:读取性能比较高。适用于遍历顺序存储集合。
            链式存储:时间复杂度太大,不适用于遍历链式存储的集合。
    (2)迭代器遍历,Iterator:
            顺序存储:如果不是太在意时间,推荐选择此方式,毕竟代码更加简洁,也防止了Off-By-One的问题。
            链式存储:意义就重大了,平均时间复杂度降为O(n),还是挺诱人的,所以推荐此种遍历方式。
    (3)foreach循环遍历:
            foreach只是让代码更加简洁了,但是他有一些缺点,就是遍历过程中不能操作数据集合(删除等),所以有些场合不使用。而且它本身就是基于Iterator实现的,但是由于类型转换的问题,所以会比直接使用Iterator慢一点,但是还好,时间复杂度都是一样的。所以怎么选择,参考上面两种方式,做一个折中的选择。
     
    Java的最佳实践是什么?
      Java数据集合框架中,提供了一个RandomAccess接口,该接口没有方法,只是一个标记。通常被List接口的实现使用,用来标记该List的实现是否支持Random Access。
    一个数据集合实现了该接口,就意味着它支持Random Access,按位置读取元素的平均时间复杂度为O(1)。比如ArrayList。
    而没有实现该接口的,就表示不支持Random Access。比如LinkedList。
    所以看来JDK开发者也是注意到这个问题的,那么推荐的做法就是,如果想要遍历一个List,那么先判断是否支持Random Access,也就是 list instanceof RandomAccess。
    比如:
    if (list instanceof RandomAccess) {
        //使用传统的for循环遍历。
    } else {
        //使用Iterator或者foreach。
    }
     来源:http://www.cnblogs.com/leskang/p/6031282.html
  • 相关阅读:
    WampServer Mysql配置
    Java实现 蓝桥杯VIP 算法提高 陶陶摘苹果2
    Java实现 蓝桥杯VIP 算法提高 陶陶摘苹果2
    Java实现 蓝桥杯VIP 算法提高 陶陶摘苹果2
    Java实现 蓝桥杯VIP 算法提高 质因数2
    Java实现 蓝桥杯VIP 算法提高 质因数2
    Java实现 蓝桥杯VIP 算法提高 质因数2
    Java实现 蓝桥杯VIP 算法提高 质因数2
    Java实现 蓝桥杯VIP 算法提高 质因数2
    Java实现 蓝桥杯VIP 算法提高 前10名
  • 原文地址:https://www.cnblogs.com/hezhiyao/p/7417423.html
Copyright © 2011-2022 走看看