Collection相关实现类及其源码分析

110 阅读10分钟

集合

集合\数组都是对于多个数据进行存储的结构, 简称java容器.

说明: 此时的存储, 主要是指内存层的存储, 不涉及持久化的存储(,txt, .jpj, .avi ,数据库等等)

数组

优点:

  1. 一旦初始化以后, 长度就确定了.

  2. 数组一旦确定好, 其元素的类型就确定了 , 我们只能操作指定类型的数据了

缺点:

  1. 一旦初始化长度就不能改变

  2. 数组的方法非常有限, 增删改查都需要自己写, 非常不方便, 同时效率不高

  3. 获取数组中实际元素的个数, 数组没有现成的属性或方法可用

  4. 数据存储的特点: 有序, 可重复. 对于无序, 不可重复的需求, 不能满足

集合框架

|------ Collection接口 : 单例集合, 用来存储一个一个对象
    |-----List接口: 存储无序的, 可以重复的数组 --> "动态"数组
        |---> ArrayList,LinkedList,Vector
    |-----Set 接口: 存储无序的, 不开重复的数组 --> 高中讲的"集合"
        |---> HashSet,LinkedHashSet,TreeSet

|-------Map接口: 双列集合, 用来存储一对(key-value)一对数据 ---> 高中函数: y = f(x)
key相当于x, value相当于y, 一个key可以对应多个value, 但是一个key不能对应多个value
        |----> HashMap, LinkedHashMap, TreeMap, HashTable, Properties

Collection接口的常用方法

例子:

    @Test
    public void test6() {
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(456);
        coll.add(new Person(23, "Tom"));
        coll.add(new String("Hello"));

        // hashCode() 获取当前对象的哈希值
        System.out.println(coll.hashCode());

        // 集合 --> 数组 toArray()
        Object[] objects = coll.toArray();
        for (int i = 0; i < objects.length; i++) {
            System.out.print(objects[i] + ", ");
        }

        // (扩展)数组  --> 集合
        List<String> strings = Arrays.asList("AA", "BB", "CC");
        System.out.println(strings);
        List<int[]> ints = Arrays.asList(new int[]{1, 2, 34});
        System.out.println(ints); // [[I@3d82c5f3] 这个被当成一个整体进行使用
        System.out.println(ints.size());
    }

    @Test
    public void test5() {
        Collection coll = new ArrayList(); //这个是有序集合, equals()对顺序有要求
        coll.add(123);
        coll.add(456);
        coll.add(new Person(12, "Bob"));
        coll.add(new String("lihua"));
        System.out.println(coll);

        Collection coll1 = Arrays.asList(123, 456, 789);

        // equals(Object obj) 要想返回ture, 要判断当前元素和形参集合需要一样(对于有序和无序集合要求不一样)
        System.out.println(coll.equals(coll1));
    }

    @Test
    public void test4() {
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(456);
        coll.add(new Person(12, "Bob"));
        coll.add(new String("lihua"));

        Collection coll1 = Arrays.asList(123, 456, 789);

        //retainAll()  求交集, 把交集的值给了调用此方法的对象
        coll.retainAll(coll1);
        System.out.println(coll);

    }

    @Test
    public void test3() {
        // remove(Object o) 从当前集合中移除元素o
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(456);
        coll.add(new Person(12, "Bob"));
        coll.add(new String("lihua"));

        System.out.println(coll.remove(123));
        System.out.println(coll);

        System.out.println(coll.remove(new Person(23, "bob")));
        System.out.println(coll);

        // removeAll(Object obj) 从当前集合中移除所以的obj元素, 相当于移除两个集合的交集
        Collection coll1 = Arrays.asList(123, 456, 789);
        System.out.println(coll.removeAll(coll1));
        System.out.println(coll);


    }
    /**
     * 向Collection接口的实现类中添加obj时, 要求obj所在的类需要重写equals()
     */
    @Test
    public void test2() {
        Collection coll = new ArrayList();
        coll.add("ABC");
        Person p = new Person(12, "Mike");
        coll.add(p);
        System.out.println(coll.contains(p));

        coll.add(new Person(12,"bob"));
        System.out.println(coll.contains(new Person(12, "bob")));

        coll.add(new String("Tom"));
        System.out.println(coll.contains(new String("Tom"))); // String重写了equals()方法

        // containsAll(Collection coll)  判断形参coll是否全部存在与当前对象
        // 方式一:
//        Collection coll2 = new ArrayList();
//        coll2.add("ABC");
//        coll2.add("www");
        // 方式二:
        Collection coll2 = Arrays.asList(123, 456); // 多态
        System.out.println(coll.containsAll(coll2)); // false

    }
    @Test
    public void test1() {
        Collection coll = new ArrayList();

        // add(Object e) 将e添加到集合coll中
        coll.add("AA");
        coll.add("BB");
        coll.add(123); // 自动装箱
        coll.add(new Date());

        // size() 添加元素的个数
        System.out.println(coll.size());

        // addAll()
        Collection coll1 = new ArrayList();
        coll1.add(456);
        coll1.add("CC");
        coll1.addAll(coll);
        System.out.println(coll1.toString()); // 这个是经过重写过得方法

        // isEmpty() 是否为空
        System.out.println(coll.isEmpty());

        // clear() 清空对象,但是不是null
        coll.clear();
        System.out.println(coll.isEmpty());
        System.out.println(coll);// []

    } 
Iterator方法

注意点:iterator不是容器, 元素还在原来的集合中

注意点:集合元素遍历操作iterator, 主要是用来遍历Collection ,而Map不用iterator来遍历

 @Test
    public void test1() {
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(456);
        coll.add(new Person(23, "Tom"));
        coll.add(new String("Hello"));

        // next()方法, 可以输出对象的内容
        /*System.out.println(iterator.next());
        System.out.println(iterator.next());
        System.out.println(iterator.next());
        System.out.println(iterator.next());
        // 超过了size()就会报错 NoSuchElementException
        // System.out.println(iterator.next());*/

        // 方式二: 不推荐
        for (int i = 0; i < coll.size(); i++) {
            System.out.println(iterator.next());
        }

        // 方式三, 推荐写法 iterator不是容器, 元素还在原来的集合中
        Iterator iterator = coll.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    } 
iteratoe错误写法

错误方式一:

@Test
    public void test4() {
        double sin = Math.sin(1.2);
        System.out.println(sin);
    }
    @Test
    public void test3() {
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(456);
        coll.add(new Person(23, "Tom"));
        coll.add(new String("Hello"));
        Iterator iterator = coll.iterator();

        while (iterator.hasNext()) {
            Object next = iterator.next();
            if ("Tom".equals(next)) {
                iterator.remove();
            }
        }
        // 需要重置指针
        iterator = coll.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }

错误方式二:

@Test
    public void test2() {
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(456);
        coll.add(new Person(23, "Tom"));
        coll.add(new String("Hello"));
        Iterator iterator = coll.iterator();

        // 错误写法一
        /*if (iterator.next() != null) {
            System.out.println(iterator.next());
        }*/

        // 错误写法二 匿名对象
        while (coll.iterator().hasNext()) { // 指针每次都会重置在第一个上面
            System.out.println(coll.iterator().next()); // 会不停的输出第一个值: 123
        } 
foreach循环遍历集合
@Test
    public void test1() {
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(456);
        coll.add(new String("Tom"));
        coll.add(new Person(23, "Tom"));

        // 增强for循环
        // 格式: for(被遍历元素的类型 自变量的命名 : 被遍历变量的名字)
        for(Object obj: coll) {
            System.out.println(obj);
        }

        int[] ints = {1, 34, 56};
        for(int i: ints) {
            System.out.println(i);
        }

        // 面试题
        String[] strings = {"MM", "MM", "MM"};

        // 方式一: 普通for
        for (int i = 0; i < strings.length; i++) {
            strings[i] = "GG";
        }

        // 方式二: foreach
        for (String s: strings) { // 这个是把strings的每一个元素赋值给String类型的s, 然后把s修改, 并没有修改strings里面的元素
            s = "KK";
        }

        for (int i = 0; i < strings.length; i++) {
            System.out.println(strings[i]);
        } 

Collection的子接口之一List接口

 *     |------ Collection接口 : 单例集合, 用来存储一个一个对象
 *          |-----List接口: 存储无序的, 可以重复的数组 --> "动态"数组
 *                 |-->ArrayList 作为List接口的主要实现类 线程不安全的, 效率高 底层使用Object[] elementData存储
 *                 |-->LinkedList 底层使用双向链表存储, 对于频繁的插入或删除,效率会比ArrayList效率高
 *                 |-->Vector  作为List接口的古老实现类  线程安全的, 效率不高 底层使用Object[] elementData存储
 *
 *
 *       1.0 ArraysList源码分析
 *       jdk7.0

 *       ArrayList list = new ArrayList();  // 底层创建了一个长度为10的 Object[] elementData数组
 *       list.add(10);  // 底层 elementData[0] = new Integer(10);
 *       .......
 *       list.add(11); // 如果此次添加导致低沉elementData长度不够, 那么就会扩容到原来的1.5倍, 同时将原有的数据复制到新建的数组
 *
 *       启发: 在开发, 如果明确需要很长的容量, 可以提前指定容量, 避免重复扩容影响效率
 *       ArrayList list = new ArrayList(int capacity);来创建指定长度
 *
 *       jdk8.0
         ArrayList的变化
 *       ArrayList list = new ArrayList();  // 底层创建了一个长度为0的 Object[] elementData数组 {}
 *
 *       list.add(123); // 第一次调用add方法, 底层才创建一个长度为10的数组
 *
 *       小结: jdk7的创建方式, 类似于单例设计模式中的饿汉式
 *            jdk8的创建方式  类似于单例设计模式中的懒汉式
 *
        2.0 
        LinkedList源码分析
        linkedList list = new LinkList() 内部声明了Node的first 和 last属性, 此时默认值为null
        list.add(123); // 将123封装的Node对象之中
        其中LinkedList中Node的定义体现了双向链表

        3.0 
        jdk7.0和jdk8.0 的vector源码分许
        vector底层都是创键了,一个长度为10的Object[]数组, 扩容默认为2倍
 *
 *面试题: ArraysList, LinkedList, Vector 三者的异同点
 *   相同点 : 都实现了List接口, 都可以存储: 有序 可重复的数组
 *   不同点 : 看上面
List接口方法
  • void add(int index, Object ele):在index位置插入ele元素
  • boolean addAll(int index, Collection eles):从index位置开始将else中的所有元素添加进来
  • Object get(int index):获取指定index位置的元素
  • int indexOf(Object obj):返回obj在集合中首次出现的位置
  • int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
  • Object remove(int index):移除指定index位置的元素,并返回此元素
  • Object set(int index, Object ele):设置指定index位置的元素为ele
  • List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex 位置的子集合

常用方法

  • 增 add()
  • 删 remove(int index) / remove(Object obj)
  • 改 set(int index, Object ele)
  • 查 get(int index)
  • 插 add (int index, Object ele)
  • 长度 size()
  • 遍历 ① Iterator 迭代器遍历
    • ② 增强for循环
    • ③ 普通for循环

测试类

    @Test
    public void test1() {
       ArrayList list = new ArrayList();
        list.add("AA");
        list.add(new Person(23, "Tom"));
        list.add("AA");
        list.add(123);
        System.out.println(list);

        // void add(int index, Object ele):在index位置插入ele元素
        list.add(1, new Person(12, "Jack"));
        System.out.println(list);

        // boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素加入进来
        List integers = Arrays.asList(1, 2, 33);
        list.add(2, integers);
        System.out.println(list);
        System.out.println(list.size()); // 如果list调用add()那么, 结果为5

    }


    @Test
    public void test2() {
        ArrayList list = new ArrayList();
        list.add("AA");
        list.add(new Person(23, "Tom"));
        list.add("AA");
        list.add(123);
        list.add(323);
        System.out.println(list);

        // int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置, 找不到返回-1
        System.out.println(list.indexOf(123)); //  找到了3
        System.out.println(list.indexOf(321)); //  没找到-1
        System.out.println(list.lastIndexOf(323)); // 从后往前找


        // Object get(int index):获取指定index位置的元素
        System.out.println(list.get(0));

        // Object remove(int index):移除指定index位置的元素,并返回此元素
        Object remove = list.remove(2);
        System.out.println(remove);
        System.out.println(list);

        // Object set(int index, Object ele):设置指定index位置的元素为ele,返回被修改的值
        Object qiu = list.set(1, "QIU");
        System.out.println(qiu);
        System.out.println(list);

        // List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex左闭右开的List, 不会修改原来的list
        List list1 = list.subList(0, 3);
        System.out.println(list1);

    }



    @Test
    public void test3() {
        ArrayList list = new ArrayList();
        list.add("AA");
        list.add(new Person(23, "Tom"));
        list.add("AA");
        list.add(123);
        list.add(323);

        // 方式一
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
        System.out.println("*******************");

        // 方式二
        for(Object obj : list) {
            System.out.println(obj);
        }
        System.out.println("*******************");

        // 方式三
        for (int i = 0; i < list.size() ;i++) {
            System.out.println(list.get(i));
        }
    }

Person类

public class Person {
    private int age;
    private String name;

    public Person() {
    }

    public Person(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        System.out.println("Person equals()....");
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Person person = (Person) o;

        if (age != person.age) return false;
        return name != null ? name.equals(person.name) : person.name == null;
    }

    @Override
    public int hashCode() {
        int result = age;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        return result;
    }
}

Collection的子接口之一Set接口

 * |------ Collection接口 : 单例集合, 用来存储一个一个对象
 *
 *          |-----Set 接口: 存储无序的, 不开重复的数组 --> 高中讲的"集合"
 *                 |--> HashSet :set接口的主要实现类, 线程不安全, 可以存储null值
 *                      |-->LinkedHashSet :作为HashSet的子类 可以按照添加的顺序进行变量
 *                 |-->TreeSet : 使用红黑数组, 添加的元素必须是同一类中对象, 可以按照对象的某些属性进行排序

 *    1. set中没有定义额外的方法, 都是Collection中定义的方法
 *
 *    2. 要求:
 *          向set中添加的数据,一定要重写hashCode()和 equals()方法 即相同的对象一定要有相同的哈希值
 *          重写两方法的小技巧: 对象中用作equals()方法比较Field, 都应该用来计算hashCode值;
特征:
HashSet
  1. 无序性 : 不等于随机性. 存储的数据在底层数组中并非按照索引顺序添加的, 而是根据数据的哈希值确定的

  2. 不可重复性 : 保证元素添加的元素按照equals()来判断, 不能返回true; 即相同的元素只能添加一个

源码分析

问题HashSet如何保证没有重复的元素这一特性 ?

HashSet为例
       我们向HashSet中添加a, 首先调用元素啊所在类的hashCode()方法, 计算元素a的哈希值, 通果过默写算法把哈希值转化为HashSet底层数组中的存放位置(即为索引位置)
       判断此位置上是否有元素:
             若没有元素, 则添加成功;   ---> 情况一
             若此位置有其他元素b(或以链表形式存在多个元素), 则比较元素a与元素bhash值:
                       若hash值不相同, 则元素a添加成功  --->情况二
                       若hash值相同, 进而判断a类中的equals()方法,看equals()的返回值
                                如果返回值为true, 那么添加失败
                                如果返回值为false, 那么添加成功  --->情况三

          对于,情况二和情况三来说, 元素a与已存在指定索引位置上的数据以链表方式存储
          对于以链表形式存放的顺序:
                    jdk7.0: 元素a放到数组中, 指向原来的元素
                    jdk8.0: 原来的元素在数组中指向元素a               
               总结: 78HashSet底层是数组 + 链表 

链表形式存放的顺序

LinkedHashSet

LinkedHashSet作为HashSet的子类, 再添加数据时,每个数据还维护两个引用记录 前一个数据和后一个数据.

优点: 对于频繁的遍历效率比HashSet效率高

TreeSet
 *    1.向TreeSet中添加的数据, 要求是相同类的对象
 *    2.两种排序方式: 自然排序 和 定制排序
 *    3. 自然排序中, 比较两个对象是否为相同标准为: CompareTo()返回0, 不再是equals().
      4. 定制排序中, 比较两个对象是否为相同标准为: Compare()返回0, 不再是equals().
  • 向TreeSet中添加的数据, 要求是相同类的对象

    底层是用红黑树

自定义的类需要继承Comparable接口, 重写comapreTo()方法

public class User implements Comparable{
    private String name;
    private int age;

    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }


    @Override
    public int compareTo(Object o) {
        if (o instanceof User) {
            User user = (User) o;
            if (user.name == this.name) {
                return user.age - this.age;
            } else {
                return user.name.compareTo(this.name);
            }
        }
        throw new RuntimeException("输入的对象不符合规范");
    }
}

测试方法

@Test
public void test() {
     set.add(new User(new String("Tom"), 12));
        set.add(new User("Jack", 15));
        set.add(new User("Mick", 10));
        set.add(new User(new String("Tom"), 99));

        System.out.println(set);

        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
} 

定制排序

@Test
    public void test2() {
        // 定制排序
        Comparator com = new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                if (o1 instanceof User) {
                    User user1 = (User) o1;
                    User user2 = (User) o2;

                    return Integer.compare(user1.getAge(), user2.getAge());
                }
                throw new RuntimeException("请输入正确的类型");
            }
        };
        TreeSet set = new TreeSet(com);
        set.add(new User("Tom", 12));
        set.add(new User("Jack", 15));
        set.add(new User("Mick", 10));  // 这个和下面的年龄一只会添加一个, 这个利用重写Compare()来比较是否相同
        set.add(new User("Micks", 10));

        System.out.println(set);

        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    } 

结果:

[User{name='Mick', age=10}, User{name='Tom', age=12}, User{name='Jack', age=15}]
User{name='Mick', age=10}
User{name='Tom', age=12}
User{name='Jack', age=15} 
链表图示
// 简单的链表演示
public class LinkListTest {
    public static void main(String[] args) {
        // 模拟一个简单的双向链表
        Node jck = new Node("Jck");
        Node tom = new Node("Tom");
        Node hsp = new Node("hsp");

        jck.next = tom;
        tom.next = hsp;

        hsp.pre = tom;
        tom.pre = jck;

        Node first = jck;
        Node last = hsp;
        // 输出数组中的元素
        // 从头遍历
        System.out.println("==从头遍历==");
        while (true) {
            if (first == null) {
                break;
            }
            System.out.println(first);
            first = first.next; // 把指针向后移
        }

        // 从后遍历
        /*System.out.println("==从后遍历==");
        while (true) {
            if (last == null) {
                break;
            }
            System.out.println(last);
            last = last.pre; // 指针向前移动
        }*/

    }
}


// 定义一个双向链表节点的对象
class Node {
    public Object itm; // 真正的数据
    public Node next; // 指向下一个节点
    public Node pre; // 指向上一个节点

    public Node(Object name) {
        this.itm = name;
    }

    @Override
    public String toString() {
        return "Node name = " + itm;
    }
}