集合框架最详解(包括底层原理)

集合框架(非常重要)

集合体系结构

在这里插入图片描述

Collection

在这里插入图片描述

在这里插入图片描述

补充:

//把集合转换为指定类型的数组,可以使用下面的代码
String[] array1 = c.toArray(new String[c.size()]);
System.out.println(Arrays.toString(array1)); //[java1,java2, java2, java3]

//把一个集合中的元素,添加到另一个集合中
Collection<String> c1 = new ArrayList<>();
c1.add("java1");
c1.add("java2");
Collection<String> c2 = new ArrayList<>();
c2.add("java3");
c2.add("java4");
c1.addAll(c2); //把c2集合中的全部元素,添加到c1集合中去
System.out.println(c1); //[java1, java2, java3, java4]

遍历方式

Collection<String> c=new ArrayList<>();
        c.add("小亮");
        c.add("小红");
        c.add("小粒");
        
        //1、迭代器遍历
        //1、调用集合iterator()方法返回Iterator对象
        Iterator<String> it = c.iterator();
        //循环遍历
        while (it.hasNext()){
            //迭代器对象.next()返回当前位置的元素
            String next = it.next();
            System.out.println(next);
        }
        
        //2、增强for循环
        for (String s : c) {
            System.out.println(s);
        }
        
        //3、Lambda表达式
        c.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });
        //简化后:
        c.forEach(s ->System.out.println(s));

对于引用类型,集合中存储的是存储的是地址

List集合

List的特点以及特有方法

在这里插入图片描述

在这里插入图片描述

 //1.创建一个ArrayList集合对象(有序、有索引、可以重复)
        List<String> list = new ArrayList<>();
        list.add("悟空");
        list.add("八戒");
        list.add("沙和尚");
        list.add("唐僧");
        System.out.println(list);//[悟空,八戒,沙和尚,唐僧]
        //2.public void add(int index, E element): 在某个索引位置插入元素
        list.add(2,"白骨精");
        System.out.println(list);//[悟空, 八戒, 白骨精, 沙和尚, 唐僧]

        //3.public E remove(int index): 根据索引删除元素, 返回被删除的元素
        System.out.println(list.remove(2));//白骨精
        System.out.println(list);//[悟空, 八戒, 沙和尚, 唐僧]
        //4.public E get(int index): 返回集合中指定位置的元素
        System.out.println(list.get(0));//悟空
        //5.public E set(int index, E e): 修改索引位置处的元素,修改后,会返回原数据
        System.out.println(list.set(2, "白龙马"));//沙和尚
        System.out.println(list);//[悟空, 八戒, 白龙马, 唐僧]

ArrayList与LinkedList的底层原理

ArrayList的底层原理

在这里插入图片描述

在这里插入图片描述

LinkedList的底层原理

底层原理是双链表

单链表:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

LinkedList的应用场景(适合在一端操作数据的情况):

在这里插入图片描述

LinkedList<String> l=new LinkedList<>();
       //入队
        l.addLast("第1号人");
        l.addLast("第2号人");
        l.addLast("第3号人");
        l.addLast("第4号人");
        System.out.println(l);
        //出队
        System.out.println(l.removeFirst());
        System.out.println(l.removeFirst());
        System.out.println(l.removeFirst());
        System.out.println(l);

在这里插入图片描述

在这里插入图片描述

 LinkedList<String> l=new LinkedList<>();
       //压栈
        l.addFirst("第1号人");
        l.addFirst("第2号人");
        l.addFirst("第3号人");
        l.addFirst("第4号人");
        System.out.println(l);
        //出栈
        System.out.println(l.removeFirst());
        System.out.println(l.removeFirst());
        System.out.println(l.removeFirst());
        System.out.println(l);

在这里插入图片描述

注意:以上的代码不唯一,只要符合数据结构的思想即可。

此外,LinkedList提供了有关栈的方法,方法内部的返回值其实就是之前代码写的:

LinkedList<String> l=new LinkedList<>();
       //压栈(push)
        l.push("第1号人");
        l.push("第2号人");
        l.push("第3号人");
        l.push("第4号人");
        System.out.println(l);
        //出栈(pop)
        System.out.println(l.pop());
        System.out.println(l.pop());
        System.out.println(l.pop());
        System.out.println(l);
Set集合

在这里插入图片描述

HashSet(无序、不重复、无索引)底层原理-哈希表

哈希值:

在这里插入图片描述

一般情况下,大概率两个对象的哈希值是不相同的,但因为hashCode()方法返回的是int类型,那么就会有一定的范围,当对象的数量超过int类型表示的范围后,会有一些对象的哈希值出现相同的情况。

JDK8之前底层原理:哈希表=数组+链表

在这里插入图片描述

无序:因为每添加一个对象,具体的位置是由一定的计算而得,所以说是无序的。

不重复:当两个元素经计算得到的位置相同,则会调用equals方法进行判断是否相等,不等则以链表的形式存入,存入的方式JDK8前后也有区别,JDK8之前是新元素替换老元素的位置,老元素则直接挂在新元素下面形成一个链表;JDK8之后,新元素直接挂在老元素的下面形成一个链表。

抛出一个问题:为什么我们在重写equals()方法时,也要重写hashCode()方法?

答:一般情况下我们重写equals()方法的目的就是为了在比较两个对象时比较的是它们的内容是否一致,那么当我们用hashSet集合去存储我们自定义类型的对象时可能会出现内容相同的对象所计算得到的哈希值不同的情况,哈希值不同了,那么它们所存储的位置就会不同,最终也就导致了一个hashSet集合中存着两个相同的内容,这也就与hashSet的不重复性冲突了。那么这时候的解决方法就是对于自定义类我们去重写它的hashCode方法,让该方法根据对象的内容来生成相应的哈希值,这也就保证了内容相同的不同对象的哈希值一定是相同的,那么在集合中所存储的位置也就一定相同,进而会调用我们重写的equals方法来判断,这解决了上述情况。

无索引:既然无序,那么也就不能根据加入的前后顺序进行检索,也就没了索引。

但是说如果哈希表中的链表过长则导致CRUD的性能下降,因此在JDK8之后引入了红黑树的结构来形成哈希表

在这里插入图片描述

了解一下数据结构中的树:

在这里插入图片描述

在这里插入图片描述

左边的树在查询是相当于一个单链表,查询性能低,所以出现了平衡二叉树。

在这里插入图片描述

LinkedHashSet(有序、不重复、无索引)底层原理

在这里插入图片描述

TreeSet(排序、不重复、无索引)底层原理

在这里插入图片描述

特别注意:自定义类型需要定义它的比较规则,两种方式,一种自定义类实现Comparable接口,另一种传入Comparator接口匿名实现类对象。

在这里插入图片描述

方式二示例:

//        Set<student> lhs=new TreeSet<>(new Comparator<student>() {
//            @Override
//            public int compare(student o1, student o2) {
//                return o1.getAge()- o2.getAge();
//            }
//        });
        //Lambda表达式简化后
        Set<student> lhs=new TreeSet<>(( o1,  o2)-> o1.getAge()- o2.getAge());
		//new student(string name,int age,double height)
        lhs.add(new student("lisa",29,180.2));
        lhs.add(new student("ss",18,180.2));
        lhs.add(new student("dd",24,180.2));
        lhs.add(new student("ff",23,180.2));
        System.out.println(lhs);

底层原理是红黑树(平衡二叉树):

 Set<String> lhs=new TreeSet<>();
        lhs.add("6");
        lhs.add("5");
        lhs.add("5");
        lhs.add("3");
        lhs.add("4");
        lhs.add("7");
        lhs.add("9");
        lhs.add("8");
        System.out.println(lhs);//[3,4,5,6,7,8,9]

形成的红黑树:

在这里插入图片描述

Collection(单列)小结:

在这里插入图片描述

注意:List还有一个subList(开始索引,结束索引)方法,包前不包后,求子集合

补充:
集合的并发修改异常

在这里插入图片描述

对于集合的并发修改异常,本质就是漏删的问题。

List<String> list = new ArrayList<>();
        list.add("王麻子");
        list.add("小李子");
        list.add("李爱花");
        list.add("张全蛋");
        list.add("晓李");
        list.add("李玉刚");
        System.out.println(list); // [王麻子, 小李子, 李爱花, 张全蛋, 晓李, 李玉刚]
        


        //需求:找出集合中带"李"字的姓名,并从集合中删除
        //第一种方式for
        for (int i = 0; i < list.size(); i++) {
            String s=list.get(i);
            if (s.contains("李")){
                list.remove(s);//此处删除后,后面的元素会移到前面来,而i的值仍要+1所以可能会出现漏删的情况
                //解决的方式是,让i在下次循环时仍然要指刚才删除的位置,故此时i--,下次循环时正好指的刚删除的位置
                i--;
            }
        }
        System.out.println(list);//[王麻子, 张全蛋]

        //第二种方式:迭代器循环
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()){
            String s=iterator.next();
            if (s.contains("李")){
                //list.remove(s);//此时调用集合的remove方法会导致并发修改异常
                iterator.remove();//迭代器自带一个remove方法,该方法可删除当前遍历元素
            }
        }
        System.out.println(list);//[王麻子, 张全蛋]

注意:增强for循环、Lambda表达式遍历集合时产生并发修改异常时无法更改修复。

分析:增强for循环本身就是对for循环的一种简写,无法进行类似于i–的操作,故无法修改;Lambda表达式中既无法做类似于i–的操作,也无法获取迭代器对象,故也无法修改。

Collection的其他操作

可变参数:

在这里插入图片描述

public static void main(String[] args){
        //不传递参数,下面的nums长度则为0, 打印元素是[]
        test();	
        
        //传递3个参数,下面的nums长度为3,打印元素是[10, 20, 30]
        test(10,20,30); 
        
        //传递一个数组,下面数组长度为4,打印元素是[10,20,30,40] 
        int[] arr = new int[]{10,20,30,40}
        test(arr); 
    }
    
    public static void test(int...nums){
        //可变参数在方法内部,本质上是一个数组
        System.out.println(nums.length);
        System.out.println(Arrays.toString(nums));
        System.out.println("----------------");
    }

注意:

最后还有一些错误写法,写代码时注意一下,不要这么写!!!

  • 一个形参列表中,只能有一个可变参数;否则会报错
  • 一个形参列表中如果多个参数,可变参数需要写在最后;否则会报错

在这里插入图片描述

Collections工具类

注意Collections并不是集合,它比Collection多了一个s,一般后缀为s的类很多都是工具类。这里的Collections是用来操作Collection的工具类。它提供了一些好用的静态方法,如下

在这里插入图片描述

在这里插入图片描述

List<String> list=new ArrayList<>();
        Collections.addAll(list,"小张","小赵","小侯","小刘");
        System.out.println(list);
        //Collections.shuffle()方法相当于洗牌,重新打乱顺序
        Collections.shuffle(list);
        System.out.println(list);
        //排序
        Collections.sort(list);
        System.out.println(list);
        //对自定义类对象排序
        List<student> list1=new ArrayList<>();
        Collections.addAll(list1,new student("lisa", 29, 180.2),new student("hh", 29, 167),
        new student("sir", 29, 170.3),new student("lihua", 29, 170.5));
        Collections.sort(list1, new Comparator<student>() {
            @Override
            public int compare(student o1, student o2) {
                return Double.compare(o1.getHeight(), o2.getHeight());
            }
        });
        System.out.println(list1);

Map集合

在这里插入图片描述

Map常用方法

在这里插入图片描述

Map集合的遍历方式

在这里插入图片描述

第一种方式:根据键找值

// 准备一个Map集合。
        Map<String, Double> map = new HashMap<>();
        map.put("蜘蛛精", 162.5);
        map.put("蜘蛛精", 169.8);
        map.put("紫霞", 165.8);
        map.put("至尊宝", 169.5);
        map.put("牛魔王", 183.6);
        System.out.println(map);
        //第一种方式:根据键值获取value值
        //调取map.keySet()方法获取集合的键值
        Set<String> key = map.keySet();
        System.out.println(key);
        for (String s : key) {
            //根据键值获取value值
            Double v = map.get(s);
            System.out.println(s+"===>"+v);
        }

第二种方式:根据“键值对”遍历

核心:调用map集合的entrySet方法返回该集合的键值对类型集合,在循环中调用getKey()和getValue()方法获取每个键值对的键和值

在这里插入图片描述

//第二种方式:把“键值对”看成一个整体进行遍历
        Set<Map.Entry<String, Double>> entries = map.entrySet();
        for (Map.Entry<String, Double> entry : entries) {
            String key = entry.getKey();//获取键值对的键
            Double value = entry.getValue();//获取键值对的值
            System.out.println(key+"---->"+value);
        }

第三种方式:Lambda表达式遍历

在这里插入图片描述

   //第三种方式:Lambda表达式
//        map.forEach(new BiConsumer<String, Double>() {
//            @Override
//            public void accept(String s, Double aDouble) {
//                System.out.println(s+"--->"+aDouble);
//            }
//        });
        map.forEach(( s,  aDouble) ->
                System.out.println(s+"--->"+aDouble)
        );
小案例:

在这里插入图片描述

 List<String> data=new ArrayList<>();
        String[] places={"A","B","C","D"};
        //随机产生80个景区选择存入集合
        Random random=new Random();
        for (int i = 1; i <= 80; i++) {
            int num = random.nextInt(4);//0,1,2,3
            data.add(places[num]);
        }
        System.out.println(data);
        //统计data集合中的各个数量,并放入map集合中
        Map<String,Integer> map=new HashMap<>();
        for (String s : data) {
            if (map.containsKey(s)){
                //更新value值
                map.put(s,map.get(s)+1);
            }else {
                map.put(s,1);
            }
        }
        System.out.println(map);
HashMap的底层原理

同样也是基于哈希表,其实之前所学的set集合内部返回的就是hashmap对象,只不过是只要了键,没要value值。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

LinkedHashMap底层原理

原来学习的LinkedHashSet集合的底层原理就是LinkedHashMap

在这里插入图片描述

首先将键值对封装成一个Entry对象,然后计算每一个的哈希值放入集合,不同于HashMap的是每一个元素之间多了一个双链表机制记录元素顺序,查找时会按照头指针进行查找。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值