java高级 - 集合(Collection)

1. 集合主要用来解决什么问题?

集合、数组都是对多个数据进行存储操作的结构,简称Java容器。

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

集合就是来解决数组中的存储数据方面的弊端
数组存储的弊端:

  • 一旦初始化以后,其长度就不可修改。

  • 数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。

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

  • 数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。

Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个个元素集合,另一种是图(Map),存储键/值对映射。

2. 第一种容器 集合(Collection)

Collection:单例集合 是用来存储一个一个的对象
它有两个子接口List接口 和 Set接口
在这里插入图片描述

1. List接口

List接口是一个有序的Collection,使用此接口能够精确的控制每个元素插入的位置,能够通过索引(元素在List中位置,类似于数组的下标)来访问List中的元素,第一个元素的索引为0,而且允许有相同的元素。

List 接口存储一组可重复,有序(插入顺序)的对象。

List接口有三个实现类:ArrayList、LinkedList、Vector

① ArrayList

ArrayList:作为List接口的主要实现类、线程不安全的、效率高、底层使用Object[]存储

ArrayList 类是一个可以动态修改的数组,与普通数组的区别就是没有固定大小的限制,我们可以添加或删除元素。

ArrayList 继承了 AbstractList ,并实现了 List 接口。

无参构造器

  1. 添加元素,第一次扩容长度为10
  2. 满了按1.5倍扩容
    在这里插入图片描述
// ArrayList的使用
Collection coll = new ArrayList();

ArrayList 是一个数组列表,提供了相关的添加、删除、修改、遍历等功能。

1. 向集合中添加元素
@Test
    public void test1(){
        // 创建数组对象
        Collection coll = new ArrayList();
        // 添加数据
        coll.add("123");
        coll.add("abc");
        coll.add("fda");
        coll.add(new Object());
        coll.add(new String("Tom"));
        coll.add(false);
        coll.add(new Person("Jerry" , 20));

        // addAll(Collection coll)方法可以将指定集合中的所有元素添加到此集合中。
        Collection coll2 = new ArrayList();
        coll2.addAll(coll);

        // 返回该集合中元素的个数。如果超过了Integer.MAX_VALUE,那么返回Integer.MAX_VALUE。
        System.out.println(coll2.size());

        // void clear() : 清空掉集合中的所有元素
        coll2.clear();

        // boolean isEmpty() :  如果集合中没有元素返回true。
        System.out.println(coll2.isEmpty());

    }
2. 在集合中查找元素
@Test
    public void test2(){
        // 创建数组对象
        Collection coll = new ArrayList();
        // 添加数据
        coll.add(123);
        coll.add("abc");
        coll.add(null);
        coll.add(new Object());
        coll.add(new String("Tom"));
        coll.add(false);
        coll.add(new Person("Jerry" , 20));// 自定义类的对象


        // 1. boolean contains(Object obj) : 如果集合中包含指定元素那么返回true。
        // 我们在判断时会调用obj对象所在类的equals()。
        // 要求:向Collection接口的实现类对象中添加数据obj时,要求obj所在类要重写equals()。
        // 特别的,如果集合中也包含NULL元素的时候并且要查找的元素也是NULL的时候也返回true。
        System.out.println(coll.contains(new String("Tom"))); // true Stirng类型比较的内容(equals)
        // 需要在Person类中重写equals方法
        System.out.println(coll.contains(new Person("Jerry", 20))); // true
        System.out.println(coll.contains(null)); // true

        // 2. boolean containsAll(Collection<?> c) : 如果该集合中包含指定集合中的所有元素的时候返回true。
        // Arrays.asList() 是将数组转成list,asList()的形参列表里面是一个 - 可变形参
        Collection coll1 = Arrays.asList(123 , "abc");
        System.out.println(coll.containsAll(coll1)); // true
    }
3. 删除集合中指定的元素
@Test
    public void test3(){
        // 3. boolean remove(Object o) : 删除集合中的指定的元素。
        // 创建数组对象
        Collection coll = new ArrayList();
        // 添加数据
        coll.add("abc");
        coll.add(123);
        coll.add(null);

        coll.add(123);
        coll.add(new String("Tom"));
        coll.add(false);
        coll.add(new Person("Jerry" , 20));

        System.out.println(coll); // [abc, 123, null, 123, Tom, false, Person{name='Jerry', age=20}]
        // boolean remove(Object obj):移除集合中指定的obj元素(调用equals()),重头开始找,找到了就删除,后面的不看,
        // 成功返回true,没找到返回false
        System.out.println(coll.remove(123)); // 删除成功true
        
        // 在我们Person重写过equlse()以后
        coll.remove(new Person("Jerry" , 20)); // 删除成功
        System.out.println(coll); // [abc, null, 123, Tom, false]

        // 4. boolean removeAll(Collection<?> c) : 删除当前集合中所有等于指定集合中的元素。
        Collection coll1 = Arrays.asList(123 , "abc");
        coll.removeAll(coll1);
        System.out.println(coll); // [null, Tom, false]
    }

	// 5. 清空集合中的数据
	coll.clear();
	System.out.println(coll);	// 输出 []
4. 仅保留该指定集合中存在的所有元素。其余删除
@Test
    public void test4(){
        // 创建数组对象
        Collection coll = new ArrayList();
        // 添加数据
        coll.add(123);
        coll.add("abc");
        coll.add(null);

        coll.add(new String("Tom"));
        coll.add(false);
        coll.add(new Person("Jerry" , 20));

        // 5. boolean retainAll(Collection<?> c):仅保留该指定集合中存在的所有元素。其余删除
        // 交集:获取当前集合和coll1集合的交集,并返回给当前集合,删除交集以外的元素
        Collection coll1 = Arrays.asList(123 , "abc" , 789);

        coll.retainAll(coll1);
        System.out.println(coll); // [123, abc]
        System.out.println(coll1); // [123, abc, 789]

        // 6. equals(Object o): 要想返回true,要求当前集合和形参集合的元素都相同
        Collection coll2 = Arrays.asList(123 , "abc" , 789);

        System.out.println(coll1.equals(coll2)); // true

    }
5. 数组集合之间的转换
@Test
    public void test5(){
        // Object[] toArray()这个方法是集合和数组转化的桥梁。
        // 集合 ---> 数组
        Collection coll = Arrays.asList(123 , "abc" , 789);
        Object[] arr = coll.toArray();
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }

        // 数组 ---> 集合
        List strings = Arrays.asList(new String[]{"abc", "123"});
        System.out.println(strings);

        List list = Arrays.asList(new int[]{123, 456}); // new int[]{123, 456} 被识别成一个元素
        System.out.println(list.size());// 1

        List list1 = Arrays.asList(new Integer[]{123, 456}); // 识别成两个元素
        System.out.println(list1.size()); // 2
    }
}
② LinkedList

LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高,底层使用双向链表存储

1. 试验双向链表
// 试验双向链表
public class LinkedList01 {
    public static void main(String[] args) {
        Node tom = new Node("Tom");
        Node jack = new Node("jack");
        Node marry = new Node("marry");

        /** 1. 链表中每个元素有三个引用
         *      (1) next:指向该元素的下一个元素
         *      (2) item:保存数据
         *      (3) pre:指向该元素的上一个元素
         */
        // next
        tom.next = jack;
        jack.next = marry;

        // pre
        marry.pre = jack;
        jack.pre = tom;

        // 头元素
        Node first = tom;   // 让first引用指向tmo,表示双向链表的头结点
        Node last = marry;  // 让last引用指向marry,表示双向链表的尾结点

        // 在tom和jack之间加入smith
        Node smith = new Node("smith");
        tom.next = smith;
        smith.pre = tom;

        smith.next = jack;
        jack.pre = smith;

        // 从头到尾遍历
        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{
    Object item;
    Node next;
    Node pre;

    public Node(Object item) {
        this.item = item;
    }

    @Override
    public String toString() {
        return "Node{" +
                "item=" + item +
                '}';
    }
}

③Vector

Vector:作为List接口的古老实现类、线程安全的、效率低;底层使用可变Object[]存储

使用无参构造器默认长度为0,满了则进行2倍扩容
使用有参构造器传入长度,满了则进行2倍扩容

2. Set接口

Set接口:存储无序的、不可重复的数据

  • 无序性:不等于随机性。存储的数据在底层数组中并非照数组索引的顺序添加,而是根据数据的哈希值决定的。(添加和取出的数据不一致,没有索引)

  • 不可重复性:保证添加的元素按照equals()方法判断,不能返回true。即:相同的元素只能添加一个。

1. HashSet

HashSet作为Set接口的主要实现类;线程不安全的;可以储存null值,但只有一个null;

  1. HashSet不保证元素是有序的,其顺序取决于hash后,再确定索引的结果。(即,不保证存入元素的顺序和取出元素的顺序是一致的)

  2. 我们向HashSet中添加元素a,首先去调用元素a所在类的hashSet()方法,计算元素a的哈希值。

  3. 此哈希值通过某种算法计算出在HashSet底层数组中存放的位置(即为,索引位置),判断数组此位置上使用已经有元素:

    • 如果此位置上没有其他元素,则元素a添加成功 —> 情况一

    • 如果此位置上有其他元素b(或以链表形式存在多个元素),则比较元素a与元素b的hash值:

      • 如果hash值不相同,则添加成功

      • 如果hash值相同,进而需要调用元素a所在类的equals()方法:
        equals()返回true,则元素a添加失败
        equals()返回false,则元素a添加成功 —> 情况二

  4. 对与添加成功的情况而言:元素a 与 已经存储在指定索引位置上的数据 以链表的方式存储。原来的元素在数组中,指向元素a

    • 要求:向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()

    • 要求:重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码

    • 重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。

  5. 结论:HashSet中不保存重复的元素 / 对象。

2. LinkedHashSet

linkedHashSet的使用:

  • 作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历

  • linkedHashSet作为HashSet的子类,在添加数据时,是按照 hashcode的值来决定元素的存储位置,同时每个数据还维护了两个引用,记录此数据前一个数据和后一个数据(链表),这使得元素看起来是以插入顺序保存的。

  • 优点:对应频繁的遍历操作,linkedHashSet效率高于HashSet

@Test
    public void test2(){
        Set set = new LinkedHashSet();
        set.add(123);
        set.add(456);
        set.add("aba");
        set.add(true);
        set.add(null);
        set.add(new User("Tom" , 18));
        set.add(new User("Tom" , 18));
        set.add(123); // 重复不会加入

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

TreeSet:可以按照添加对象的指定属性,进行排序。

  1. 向TreeSet中添加的数据,要求是相同类的对象。

  2. 两中排序方式:自然排序(实现Comparable接口) 和 定制排序。

  • 自然排序中,比较两个对象是否相同的标准:compareTo()返回0 不再是前面的使用equals().

  • 定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals().

@Test
    public void test1(){
        TreeSet set = new TreeSet();
        // 失败,不能添加不同类的对象
//        set.add(123);
//        set.add(456);
//        set.add("aba");
//        set.add(true);
//        set.add(null);
//        set.add(new User("Tom" , 18));

        // 成功:举例一
//        set.add(123);
//        set.add(-123);
//        set.add(23);
//        set.add(73);
//        set.add(54);

        // 成功:举例二
        set.add(new User("zone" , 64));
        set.add(new User("anakng" , 18));
        set.add(new User("aane" , 18));
        set.add(new User("jack" , 54));
        set.add(new User("jian" , 78));
        set.add(new User("zone" , 3));

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

自然排序:
在向TreeSet中添加的对象类中,重写compareTo()

// 按姓名从大到小,年龄从小到大排序
    @Override
    public int compareTo(Object o) {
        if (o instanceof User){
            User user = (User)o;
            int compare = -this.name.compareTo(user.name);
            if (compare != 0){
                return compare;
            }else {
                return -Integer.compare(this.age , user.age);
            }
        }else {
            throw new RuntimeException("输入类型不匹配!");
        }
    }

定制排序:

@Test
    public void test2(){
        Comparator comparator = new Comparator() {

            // 按照年龄大小排序
            @Override
            public int compare(Object o1, Object o2) {
                if (o1 instanceof User && o1 instanceof User){
                    User u1 = (User) o1;
                    User u2 = (User) o2;
//                    return u1.getName().compareTo(u2.getName());
                    return Integer.compare(u1.getAge() , u2.getAge());
                }else {

                    throw new RuntimeException("输入的类型不匹配!");
                }
            }
        };

        // 自然排序,使用的是排序对象重写的 compareTo()方法
        TreeSet set = new TreeSet();

        // 定制排序,使用的是实现了Comparator接口 重写了其中的 compare()
//        TreeSet set = new TreeSet(comparator);
        set.add(new User("Tom",12));
        set.add(new User("Jerry",32));
        set.add(new User("Jim",2));
        set.add(new User("Mike",65));
        set.add(new User("Marry",33));
        set.add(new User("Jack",33));
       // 若排序中只是将 其中的值(不是全部属性) 进行了排序,当相等时,TreeSet认为他们是同一个元素,添加失败
        // 若想添加成功,则需要二级排序,将全部的属性进行排序
        set.add(new User("Jack",56));


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

3. 开发中如何选择集合实现类?

  1. 先判断存储类型(一组对象[单列]或一组键值对[双列])
    • 一组对象[单列]
      • 允许重复:List
        • 增删多:LinkedList 底层维护了一个双向链表
        • 改查多:ArrayList底层维护了Object 的可变数组
      • 不允许重复:Set
        • 无序:HashSet 底层是HashMap 实现了[数组 + 链表 + 红黑树]
        • 有序:TreeSet 底层使用TreeMap 维护的是 [红黑树] 结构
        • 插入数据和取出数据一致:LinkedHashSet 底层维护了 [数组 + 双向链表]
    • 一组键值对[双列]:Map
      • 键无序:HashMap
      • 键排序:TreeMap
      • 键插入和取出顺序一致:LinkedHashMap
      • 读取文件:Properties
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值