Java学习笔记系列05 - 集合框架

        集合是存储多个元素的容器,但由于数据结构不同,java就提供了多种集合类。而这多种集合类有共性的功能,所以通过不断的向上抽取,最终形成了集合体系结构。


        数组与集合的区别:
        数组:
长度是固定的,存储的是同一类型的元素,可以存储基本数据类型。
        集合:长度是可变的,存储的都是对象,而且对象的类型可以不一致。
        细节:集合中存储的其实都是对象的地址,不可以存储基本数值,但JDK1.5有了包装类后,可以进行存储(自动装箱)。

Java学习笔记系列05 - 集合框架



1.Collection接口

        有两个常用的子接口:List(列表),Set(集)

1.1 List接口

        有序(存入和取出的顺序一致),元素都有索引,可以重复,共性特点是都可以操作角标获取元素进行crud(增删改查)。


        Vector:数据结构是数组,数组可以增长,无论查询还是增删都很慢,效率很低,已被ArrayList替代,线程同步。
        ArrayList:数据结构是数组,长度是可变的(原理是新建数组+复制数组),查询速度很快,增删较慢,线程不同步。
        LinkedList:数据结构是链表,增删速度很快,查询较慢,线程不同步。
        特点:围绕头和尾展开定义方法,可用于实现堆栈和队列数据结构。

1.2 Set接口

        无序(存入和取出的顺序可能不一致),不能存储重复元素,保证元素的唯一性,而且方法和Collection一致,取出元素的方式只有迭代器一种。


        HashSet:数据结构是哈希表,不保证顺序,查询速度很快,线程不同步。
        TreeSet:数据结构是二叉树,可以对Set集合中的元素进行排序,线程不同步。
        LinkedHashSet:HashSet的子类,保证元素唯一性和查询效率的同时元素有序


        HashSet和TreeSet保证元素唯一性的方式:
        HashSet:通过覆盖hashCode()和equals()方法来完成

        如果元素的hashCode值不同,那就不用判断equals方法,直接存储到哈希表中,从而提高元素比较的速度。
        如果元素的hashCode值不同,才需要继续判断equals方法是否为true,如果为true则视为相同元素不存储;如果为false则视为不同元素进行存储,继续通过算法向旁边位置延伸—拉链法。
        记住:如果元素要存储到HashSet集合中,必须覆盖hashCode方法和equals方法。


        TreeSet:用于对Set集合进行元素的指定顺序排序,排序需要依据元素自身具备的比较性;如果元素不具备比较性,在运行时会发生ClassCastException异常
        所以需要实现Comparable或Comparator接口,通过覆盖其比较方法来排序,根据比较方法返回的结果判断两元素是大于小于还是等于,如果等于则视为相同元素不存储来保证元素唯一性。

        排序的方式有两种:
                排序方法一:让元素自身具备比较功能
                        需要实现Comparable接口,覆盖compareTo方法,这种方式也称为元素的自然顺序或者默认顺序。
                        如果元素自身不具备自然顺序,或者具备的自然排序不是我们所需要的,这时只能使用第二种方式。
                排序方法二:让集合自身具备比较功能,比较器排序
                        定义一个类实现Comparator接口,覆盖compare方法,将该类对象作为参数传递给TreeSet集合的构造方法。


        Set结论:往Set中存储对象时,通过该对象都需要覆盖hashCode和equals方法,并可能要实现Comparable或者Comparator接口,建立对象的排序,还要复写toString方法;当两种排序都存在时,以比较器为主,因为较为灵活。


2.迭代器Iterator

        Iterator接口就是对Collection中所有容器进行元素获取的公共接口,因为Collection中有iterator()方法,所以每一个子类集合对象都具备迭代器。
        该对象必须依赖于具体容器,因为每一个容器的数据结构都不同,所以该迭代器对象是在容器中进行内部实现的。


        注意:1.迭代器的next()方法是自动向下取元素的,要小心避免NoSuchElementException
                     2.迭代器的next()方法返回值类型是Object,获取时要使用元素的特有功能的话,要记得做类型转换(使用泛型就可以不用强转了)。
        小补充:Enumeration:古老的输出方式,迭代Vector元素,被Iterator取代。

2.1 List特有的迭代器ListIterator

        在List列表元素迭代过程中,使用了集合的方法对元素进行操作,比如满足条件添加新元素,会发生ConcurrentModificationException(并发修改异常)
        导致原因:集合引用和迭代器引用在同时操作元素。
                            在迭代中,使用了集合的方法对元素操作,导致迭代器并不知道集合中的变化,引发了数据的不确定性,导致了异常。
        解决:迭代时不能用集合的方法操作元素,既然是在迭代中操作元素,就该用迭代器的方法操作,不过Iterator的方法只有hasNext,next,remove;
                    这时需要使用Iterator的子接口ListIterator,通过List接口的listIterator()方法来获取。
        记住:该列表迭代器只有List接口有,而且这个迭代器能在迭代过程中做增删改查动作。


3.Map接口

        与Collection在集合框架中属于并列存在,存储的是一对元素:键key和值value。键值对之间有对应(映射)关系,必须要保证键的唯一性。


        Hashtable:数据结构是哈希表,不允许null作为键和值,被HashMap替代,线程同步。
        HashMap:数据结构是哈希表,允许null作为键和值,线程不同步。
        TreeMap:数据结构是二叉树,可以对Map集合中的键进行排序,线程不同步。
        LinkedHashMap:基于链表+哈希表,可以保证Map集合有序(存入和取出的顺序一致)。
        Properties:底层结构依然是哈希表,配置文件信息的属性集,键值对都是字符串,而且可以结合IO流进行键值对的操作。


        Map获取所有键值对的思路:
        Map没有迭代器,Collection具备迭代器,只要将Map转成Set,就能用迭代器获取了;转成Set是因为Set集合底层使用的就是Map集合。


        获取方式一:keySet()方法
        将Map中的键存入到Set集合,因为Set具备迭代器,所以可以用迭代方式获取到所有的键,在用get方法获取到键所对应的值。
        Set keySet = map.keySet();
        for(Iterator it = keySet.iterator(); it.hasNext(); ) {
                Object key = it.next();
                Object value = map.get(key);
                System.out.println(key+“:”+value);
        }


        获取方式二:entrySet()方法
        将Map集合中的映射关系存储到了Set集合中,这个关系的数据类型就是Map.Entry。
        Set entrySet = map.entrySet();
        for(Iterator it = entrySet.iterator(); it.hasNext(); ) {
                Map.Entry me = (Map.Entry) it.next();
                System.out.println(me.getKey()+“:”+me.getValue());
        }


        Entry是Map接口中的内部类接口,定义在Map内部的原因:
        因为只有有了Map集合,有了键值对,才会有键和值的映射关系,该关系属于Map集合中的一个内部事物,而且该事物在直接访问Map集合中的元素。


        Map集合与Collection集合的区别:
        1.Map称为双列集合,Collection称为单列集合。
        2.Map一次添加一对元素,Collection一次添加一个元素。
        3.Map存储元素使用put方法,Collection存储元素使用add方法
        4.Map没有直接获取所有元素的方法,要先转成Set集合,再用迭代器获取;
        Collection获取元素使用的就是迭代器。


4.集合的使用技巧

        (1)保证唯一就用Set,不保证唯一就用List。
        (2)看到ArrayList就是数组结构,有角标,查询速度很快。
        (3)看到Link就是链表结构,增删速度快,而且有特有方法。
        (4)看到Hash就是哈希表,就要想到哈希值,保证唯一性,存入该结构必须覆盖hashCode和equals方法。
        (5)看到Tree就是二叉树,就要想到用排序和比较。
        (6)LinkedHashSet和LinkedHashMap这两个集合保证哈希表有序。
        (7)分析问题时出现对应关系,立即想到Map集合,如果对应关系有序,想到数组。
             注意:如果对应关系个数不确定,还是以Map为主。


5.Collections

        集合框架中用于操作集合对象的工具类,都是静态的工具方法。


        将非同步集合变成同步集合的方法:Xxx synchronizedXxx(xxx)
                                                                          List synchronizedList(list);
                                                                          Map synchronizedMap(map);
        原理:定义一个类,将集合所有的方法加一把锁后返回。


        Collection和Collections的区别:
        Collection:
是java.util.下的接口,是各种集合结构的父接口,提供了各种对集合的操作,如增删改查。
        Collections:是java.util下的类,是针对集合类的工具类,提供一系列静态方法,实现对集合的查找、排序、替换、线程安全化(将非同步集合转成同步)等操作。


6.Arrays

        用于操作数组的工具类,都是静态的工具方法。


        asList方法:将数组转成List集合,可以用List集合的方法操作数组中的元素。
        注意(局限性):数组长度是固定的,转成List集合长度也是固定的,不要使用增删等会改变数组长度的方法,用了会发生不支持操作异常UnsupportedOperationException
        如果数组存的是引用数据类型,可直接作为集合的元素直接用集合方法操作。
        如果数组存的是基本数据类型,需要用包装类的类型存值,否则asList会将数组对象作为集合元素存在。


        toArray方法:集合变数组,可以限制对元素的增删操作。
        如果传递的数组长度小于集合的长度,会自动创建一个同类型的数组,长度为集合的长度。
        如果传递的数组长度大于集合的长度,就会直接使用这个数组,没有存储元素的位置为null。
        所以数组长度应该定义和集合长度一致。