Day02_Iterator&&List&&数组

接着昨天的Collection继续:

  • Object[] toArray()
    将集合转换成数组
public class CollectionDemo1 {

    public static void main(String[] args) {

        Collection c = new ArrayList();
        c.add("hello");
        c.add("world");
        c.add("java");

        Object[] objs = c.toArray();
        for (int i = 0; i < objs.length; i++) {
            String s = (String) objs[i];
            System.out.println(s.toUpperCase());        // HELLO
                                                        // WORLD
                                                        // JAVA   
        }
        objs[1] = "WORLD";
        System.out.println(Arrays.toString(objs));  // [hello, WORLD, java]
        System.out.println(c);  // [hello, world, java]
    }
}

由代码可以看出,此方法相当于在遍历的过程中额外复制了一份,objs数组和集合c没有关联,满足不了对集合修改的需求。

因此,出现了Iterator iterator()。


  • Iterator iterator()
    对 collection 进行迭代的迭代器,提供统一遍历的操作。所有collection都有此方法
  • Iterator接口
    boolean hasNext()
    E next() 返回迭代的下一个元素
    void remove() 删除元素
public class CollectionDemo2 {

    public static void main(String[] args) {

        Collection c = new ArrayList();
        c.add("hello");
        c.add("world");
        c.add("java");

        // E next()
        Iterator it1 = c.iterator();
        System.out.println(it1.next());
        System.out.println(it1.next());
        System.out.println(it1.next());
        // System.out.println(it1.next());  // NoSuchElementException异常

        // boolean hasNext()
        Iterator it2 = c.iterator();
        while (it2.hasNext()) {
        String s = (String) it2.next();
        System.out.println(s);

        // void remove() 删除最近返回的元素
        Iterator it3 = c.iterator();
        // it3.remove(); // IllegalStateException异常,因为还没有返回元素
        it3.next();
        it3.next();
        it3.remove();
        // it3.remove();  // IllegalStateException异常
        System.out.println(c);  // [hello, java]
    }
}

ArrayList的迭代器模型
在这里插入图片描述


关于并发修改异常的问题:

练习:用迭代器遍历的过程中,删除"java"。

public class CollectionDemo3 {

    public static void main(String[] args) {

        Collection c = new ArrayList();
        c.add("hello");
        c.add("world");
        c.add("java");
		
		// 练习:删除"java"
        /*
        Iterator it = c.iterator();
        while (it.hasNext()) {
            String s = (String) it.next();
            if ("java".equals(s)) {
                it.remove();  // [hello, world]
                // c.remove(s);  // ConcurrentModificationException并发修改异常
            }
        }
        System.out.println(c);
        */
		
		// 另外一个问题
        Iterator it1 = c.iterator();
        while (it1.hasNext()) {
            String s = (String) it1.next();
            System.out.println(s);
        }

        // ...茫茫多的代码
        // ...

        // 删除"hello"
        Iterator it2 = c.iterator();
        while (it2.hasNext()) {
            String s = (String) it2.next();
            if ("hello".equals(s)) {
                it1.remove();  // ConcurrentModificationException并发修改异常
            }
            System.out.println(c);
        }
    }
}

我们看到,在练习中使用集合的API进行"java"的删除操作时,会出现并发修改异常

那么,使用c.remove(s)为什么会出现并发修改异常呢?

因为c.remove(s)是在原集合中直接删除"java",此时迭代器的cursor(光标)依然位于"java"的右边,没有被修改,为非法位置,因此抛出并发修改异常。
在这里插入图片描述
由此得出一个结论:
如果用集合的API修改了集合的结构, 那么所有迭代器会失效。

另外一个问题,上面这段代码,在用it2遍历时,不小心把it2.remove();写成it1.remove();,那么就会报错:ConcurrentModificationException 并发修改异常。也就是说,你在用一个迭代器遍历时,只能访问这个迭代器的变量。

由此得出另外一个结论:
如果用某个迭代器修改了集合的结构,那么所有其它迭代器会失效。

所以说,**用迭代器遍历集合的时候,不要使用while循环,可以使用for循环,最好使用foreach循环。**因为for循环中的变量只在本循环体内起作用。

public class CollectionDemo3 {
    public static void main(String[] args) {
        Collection c = new ArrayList();
        c.add("hello");
        c.add("world");
        c.add("java");
        
        /*Iterator it1 = c.iterator();
        while (it1.hasNext()) {
            String s = (String) it1.next();
            System.out.println(s);
        }*/
        for(Iterator it = c.iterator(); it.hasNext();) {
            String s = (String) it.next();
            System.out.println(s);
        }
        
        // ...茫茫多的代码
        // ...
        
        // 删除"hello"
        /*Iterator it2 = c.iterator();
        while (it2.hasNext()) {
            String s = (String) it2.next();
            if ("hello".equals(s)) {
                it1.remove();
            }
        }*/
        for(Iterator it = c.iterator(); it.hasNext(); ) {
            String s = (String) it.next();
            if ("hello".equals(s)) {
                it.remove();
            }
        }
        System.out.println(c);
    }
}


下面我们看一下Iterator接口的实现原理:
在这里插入图片描述


List接口

List是Collection的子接口

概述:

  • 有序的 collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。

  • 与 set 不同,列表通常允许重复的元素。更确切地讲,列表通常允许满足 e1.equals(e2) 的元素对 e1 和 e2,并且如果列表本身允许 null 元素的话,通常它们允许多个 null 元素。

API:(以下为List包含的额外的API)

  • 增:
    boolean add(E e)
    向列表的尾部添加指定的元素
    void add(int index, E element)
    在指定的索引位置添加元素;索引范围:[0,list.size()]
    boolean addAll(int index, Collection c)
    索引范围:[0,list.size()]

  • 删:
    E remove(int index)
    删除指定索引上的元素;索引范围:[0,list.size()-1]

  • 改:
    E set(int index, E element)
    用element元素替换指定index位置的元素,并把被替换的元素返回;索引范围:[0,list.size()-1]

  • 查:
    E get(int index)
    获取指定索引位置上的元素;索引范围:[0,list.size()-1]
    int indexOf(Object o)
    获取与指定对象o相等的第一个元素的索引,如果不存在这样的元素,那么返回-1
    int lastIndexOf(Object o)
    获取与指定对象o相等的最后一个元素的索引,如果不存在这样的元素,那么返回-1

  • 遍历:
    ListIterator listIterator()
    光标后面元素索引为0
    ListIterator listIterator(int index)
    光标后面元素索引为index

我们先来看一下ListIterator接口,此接口继承自Iterator

概述:

列表迭代器,允许程序员按任一方向遍历列表、迭代期间修改列表,并获得迭代器在列表中的当前位置。

API:

  • 遍历:
    正向遍历
    boolean hasNext()
    // 以正向遍历列表时,如果列表迭代器有多个元素,则返回 true。
    E next()
    // 返回列表中的下一个元素。
    逆向遍历
    boolean hasPrevious()
    // 以逆向遍历列表时,如果列表迭代器有多个元素,则返回 true。
    E previous()
    // 返回列表中的前一个元素。
public class ListDemo2 {

    public static void main(String[] args) {

        List list = new ArrayList();
        list.add("hello");
        list.add("world");
        list.add("java");

        // 正向遍历
        /*for (ListIterator it = list.listIterator(); it.hasNext();) {

            String s = (String) it.next();
            System.out.println(s);
        }*/

        // 逆向遍历
        for (ListIterator it = list.listIterator(list.size()); it.hasPrevious();) {

            String s = (String) it.previous();
            System.out.println(s);
        }
    }
}
  • 获取当前光标位置:
    int nextIndex()
    // 获取光标后面元素的索引
    int previousIndex()
    // 获取光标前面元素的索引
public class ListDemo2 {

    public static void main(String[] args) {

        List list = new ArrayList();
        list.add("hello");
        list.add("world");
        list.add("java");

        // ListIterator it = list.listIterator();
        // System.out.println(it.previousIndex());  // -1
        // System.out.println(it.nextIndex());  // 0

        // ListIterator it = list.listIterator(list.size());
        // System.out.println(it.previousIndex());  // 2
        // System.out.println(it.nextIndex());// 3

        ListIterator it = list.listIterator(list.size());
        it.previous();  // previous会向前移动1个光标
        System.out.println(it.previousIndex());  // 1
        System.out.println(it.nextIndex());// 2
    }
}
  • 修改:
    void add(E e)
    // 在光标后面的位置添加元素
public class ListDemo2 {

    public static void main(String[] args) {

        List list = new ArrayList();
        list.add("hello");
        list.add("world");
        list.add("java");
 
        // void add(E e)  在光标后面\前面添加元素

        // 在"hello"后面添加"kitty"
        for (ListIterator it = list.listIterator(); it.hasNext();) {

            String s = (String) it.next();
            if ("hello".equals(s)) {
                it.add("kitty");
            }
        }
        System.out.println(list);
        
        // 在"java"前面添加"kitty"
        for (ListIterator it = list.listIterator(list.size()); it.hasPrevious();) {

            String s = (String) it.previous();
            if ("java".equals(s)) {
                it.add("kitty");
            }
        }
        System.out.println(list);  
        
		// 在"hello"后面添加"kitty", 出现ConcurrentModificationException的情况
		// 用集合的API修改了集合的结构, 那么所有迭代器会失效。
        for(ListIterator it = list.listIterator(); it.hasNext(); ) {
            String s = (String) it.next();
            if ("hello".equals(s)) {
                // 获取添加元素的索引
                int index = it.nextIndex();
                list.add(index, "kitty");  // 调用集合的API
            }
        }
        System.out.println(list);      
             
    }
}

void remove()
// 删除最近返回的元素

public class ListDemo2 {

    public static void main(String[] args) {

        List list = new ArrayList();
        list.add("hello");
        list.add("world");
        list.add("java");

        // void remove()

        // IllegalStateException 非法状态异常,因为最近没有返回元素
        /*ListIterator it = list.listIterator();
        it.remove();
        System.out.println(list);*/

        //IllegalStateException 非法状态异常 最近返回的元素移除之前不能修改集合的结构
        /*ListIterator it = list.listIterator();
        it.next();
        it.add("Allen");
        it.remove();
        System.out.println(list);*/

        // 删除"hello"
       for(ListIterator it = list.listIterator(); it.hasNext(); ) {
            String s = (String) it.next();
            if ("hello".equals(s)) {
                it.remove();                  
            }
        }
        System.out.println(list);
        
        // ConcurrentModificationException 并发修改异常
        // 用集合的API修改了集合的结构, 那么所有迭代器会失效。
		for(ListIterator it = list.listIterator(); it.hasNext(); ) {
            String s = (String) it.next();
            if ("hello".equals(s)) {                
                // 获取删除元素的索引
                int index = it.previousIndex();  // 获取光标前面元素的索引
                list.remove(index);  // 删除这个索引
            }
        }
        System.out.println(list);
        
    }
}

void set(E e)
// 替换最近返回的元素

public class ListDemo2 {

    public static void main(String[] args) {

        List list = new ArrayList();
        list.add("hello");
        list.add("world");
        list.add("java");

        //将"hello"修改成"HELLO"
        for (ListIterator it = list.listIterator(); it.hasNext(); ) {
            if ("hello".equals(it.next())) {
                it.set("HELLO");
            }
        }
        System.out.println(list);

        //调用List的set方法并不会报错并发修改异常
        //为什么呢?
        //只有通过List 修改 集合的结构 的时候才会报错 ConcurrentModificationException
        //现在只是改了集合某个位置的元素值,集合结构 并没有增加一个元素或减少一个元素
        //所以并不会报错
        for (ListIterator it = list.listIterator(); it.hasNext(); ) {
            if ("hello".equals(it.next())) {
                int index = it.previousIndex();
                list.set(index,"HELLO");
            }
        }
        System.out.println(list);
    }
}

讲完ListIterator接口,继续来看List接口的最后一个API:

  • 截取子串:
    List subList(int fromIndex, int toIndex) 包左不包右
    // 返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图。
public class ListDemo3 {

    public static void main(String[] args) {

        List list = new ArrayList();
        list.add("hello");
        list.add("world");
        list.add("java");

        List subList = list.subList(1, 3);
        System.out.println(subList);  // [world, java]

        //subList和list共享一份数据
        subList.set(0, "WORLD");
        System.out.println(subList);  // [WORLD, java]
        System.out.println(list);  // [hello, WORLD, java]
    }
}

为什么sublist和list的值都改变了呢?

因为subList和list共享一份数据。我们把这种技术称之为视图技术

去看ArrayList中subList的源码:

    public List<E> subList(int fromIndex, int toIndex) {
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList(this, 0, fromIndex, toIndex);
    }

subList方法只是返回了一个SubList对象,再找到SubList的源码,发现SubList是一个成员位置内部类。ArrayList底层其实就是一个数组,但是看SubList的四个成员变量,它并没有维护一个数组,说明它的数据就是通过访问外部类的数据而得到的。

利用成员位置内部类取访问外部类,这就是视图技术的原理。


数组

Q1: 数组我们都很熟悉,那你理解的数组是什么样的呢?它的最主要特点是什么呢?

数组的本质是固定大小的连续的内存空间,并且这片连续的内存空间又被分割成等长的小空间。它最主要的特点是随机访问

  • 数组的长度是固定的
  • 数组只能存储同一种数据类型的元素

注意:在Java中只有一维数组的内存空间是连续,多维数组的内存空间不一定连续。

那么数组又是如何实现随机访问的呢?

  • 寻址公式:i_address = base_address + i * type_length

Q2: 为什么数组的索引是一般都是从0开始的呢?

假设索引不是从0开始的,而是从1开始的,那么我们有两种处理方式:

  • 寻址公式变为: i_address = base_address + (i – 1) * type_length

  • 浪费开头的一个内存空间,寻址公式不变。

在计算机发展的初期,不管是CPU资源,还是内存资源都极为宝贵,所以在设计编程语言的时候,索引就从0开始了,而我们也一直延续了下来。

扩展:现在的编程语言有些索引是从1开始的,甚至有些编程语言还支持负数索引。

Q3: 为什么数组的效率比链表高?

CPU、内存和IO设备,它们传输数据的速率是存在很大差异的。
CPU一天,内存一年;内存一天,IO十年。

那么根据木桶理论:木桶能装多少水,取决于最短的那块木板。程序的性能主要取决于IO设备的性能?也就是说,我们提升CPU和内存的传输速率收效甚微。

实际是这样的吗?当然不是!那我们是怎么缓解它们之间的速率差异的呢?

CPU 和 内存

  • 高速缓存(预读)

    CPU不直接从内存中读取数据,而是从高速缓存中读取数据。高速缓存从内存中读取的不只是你想要的数据,而是把和这条数据相邻的数据都读取进来。

    时间局部性:刚刚访问过的这条数据很可能下一次还会访问。
    空间局部性:刚刚访问过的这条数据,很可能内存中相邻的数据会访问这条数据相邻的一条数据。

  • 编译器的指令重排序

    加快机器指令执行的效率。
    保证在单线程的环境下,执行结果和顺序执行结果一致。

内存和 IO

  • 缓存
    将磁盘上的数据缓存在内存。

CPU 和 IO

  • 中断技术
  • DMA

数组可以更好地利用CPU的高速缓存!

因为数组中的数据数连续存放的,链表中的数据不是连续存放的。根据局部性原理,会优先加载到缓存中。(局部性原理是指CPU访问存储器时,无论是存取指令还是存取数据,所访问的存储单元都趋于聚集在一个较小的连续区域中)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值