Java学习笔记 | JavaSE基础语法:3万字长文详细介绍单列集合Collection | List、ArrayList、LinkedList | 栈Stack、队列Queue | 数据结构

Alt
在这里插入图片描述

🙋大家好!我是毛毛张!
🌈个人首页: 神马都会亿点点的毛毛张

Alt

📌本篇博客是Java笔记整理集合篇的第一篇✨,在上周毛毛张分享过一张二叉树的遍历的文章,毛毛张在刷题的过程,里面涉及到集合中的Stack、Queue、List、LinkedList和ArrayList相关的知识点,相信大家在看完这篇博客之后再去刷LeetCode的相关题目的时候就会得心应手😁

🔖在没看到毛毛张这篇博客之前,大家跟着视频的学习是从ArrayList集合开始,先有一个初步的认识,但是毛毛张今天将从自顶向下,从ArrayList的顶层接口Collection开始介绍,首先介绍整个集合的继承体系✅,再来分说每个具体的实现类的使用💥

0.数据结构概述

🎃数据结构概述:数据结构是计算机底层存储、组织数据的方式。是指数据相互之间是以什么方式排列在一起的。数据结构是为了更加方便的管理和使用数据,需要结合具体的业务场景来进行选择。一般情况下,精心选择的数据结构可以带来更高的运行或者存储效率

1.数据结构之栈和队列【记忆】

  • 栈结构
    ​ 后进先出,先进后出
  • 队列结构
    ​ 先进先出,后进后出

2.数据结构之数组和链表【记忆】

  • 数组结构
    ​ 查询快、增删慢
  • 队列结构
    ​ 查询慢、增删快

1.集合

1.1 概述

  • 定义:集合是一个存放对象的引用的容器
  • 在Java中集合位于java.util包下

1.2 数组和集合的区别【5点】

  • 相同点
  • 数组和集合都是Java中的容器,可以存储多个数据
  • 不同点
    • 数组的长度是固定的,集合的长度是可变的
    • 数组只能存储相同数据类型的数据,可以是基本数据类型,也可以是引用类型
    • 集合可以存储不同数据类型的对象的引用,但一般情况下,我们会使用泛型来约定只使用1种数据类型
    • 集合只能存引用数据类型,如果要存基本数据类型,需要存对应的包装类

1.3 集合的分类【2类4种】

  • Java中集合主要分为两类四种:
    • 单列集合Collection
      • List集合
      • Set 集合
      • Queue集合
    • 双列集合Map
  • 继承关系图:
    • 大致分类:

      在这里插入图片描述
      image-20240514174350018

    • 单列集合Collection

      AbstractCollection<E>
      AbstractList<E>
      AbstractSequentialList<E>
      AbstractSet<E>
      ArrayBlockingQueue<E>
      ArrayList<E>
      «Interface»
      Collection<E>
      «Interface»
      Deque<E>
      HashSet<E>
      «Interface»
      Iterable<T>
      LinkedHashSet<E>
      LinkedList<E>
      «Interface»
      List<E>
      «Interface»
      Queue<E>
      «Interface»
      Set<E>
      «Interface»
      SortedSet<E>
      Stack<E>
      TreeSet<E>
      Vector<E>
      image-20240514172251043
    • 双列集合Map

AbstractMap<K, V>
Dictionary<K, V>
HashMap<K, V>
Hashtable<K, V>
LinkedHashMap<K, V>
«Interface»
Map<K, V>
Properties
«Interface»
SortedMap<K, V>
TreeMap<K, V>
image-20240514165011376

1.3 总结

  1. Java 集合框架主要包括两种类型的容器:一种是集合(Collection),存储一个元素集合;另一种是图(Map),存储键/值对映射
    • 单列集合的根接口是Collection,它又继承了迭代接口Iterable
      • Collection 接口又有 3 种子类型:List、Set 和 Queue,再下面是一些抽象类,最后是具体实现类
        • List接口常用的实现类有:ArrayList、LinkedList、Vector
        • Set接口常用的实现类有:HashSet、LinkedHashSet、TreeSet
        • Queue接口常用的实现类有:LinkedList、ArrayBlockQueue
    • 双列集合的根接口是Map,双列集合没有继承迭代接口Iterable
      • Map接口常用的实现类有:HashMap、HashTable、TreeMap
  2. 集合框架是一个用来代表和操纵集合的统一架构。所有的集合框架都包含如下内容:
    • 接口: 是代表集合的抽象数据类型。之所以定义多个接口,是为了以不同的方式操作集合对象
    • 实现(类): 是集合接口的具体实现。从本质上讲,它们是可重复使用的数据结构。
    • 算法: 是实现集合接口的对象里的方法执行的一些有用的计算,例如:搜索和排序,这些算法实现了多态,那是因为相同的方法可以在相似的接口上有着不同的实现。
  • 集合体系框架如图所示:
    image-20240514175155941

2 Collection

2.1 概述

  • Collection是单例集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素,并且里面的存储的元素是无序的、可重复的
    • List系列集合:添加的元素是有序、可重复、有索引
    • Set系列集合:添加的元素是无序、不重复、无索引
  • JDK 不提供此接口的任何直接实现,而是提供更具体的子接口(如Set、List和Queue)实现
  • Collection是一个接口,不能直接创建它的对象,只能通过多态的方式创建它的实现类的对象。其具体的实现类有:ArrayList、LinkedList、Vector、HashSet、LinkedHashSet、TreeSet

2.2 常用方法

方法名说明
int size()返回集合中的元素个数
boolean isEmpty()判断集合是否为空,如果为空则返回true
boolean add(E e)将指定元素添加到集合中
boolean addAll(Collection<? extends E> c)将指定集合中的所有元素添加到此集合中
boolean remove(Object o)从集合中移除指定的元素
boolean removeIf(Object o)根据条件进行移除
boolean contains(Object o)判断集合中是否存在指定的元素
void clear()清空集合中的元素
Stream<E> stream()返回以此集合的Stream流对象
Object[] toArray()返回包含此集合中所有元素的数组
  • 代码演示:

    /*添加基本数据类型*/
    import java.util.ArrayList;
    import java.util.Collection;
    
    public class Test {
        public static void main(String[] args) {
            /*
                | boolean add(E e)           | 添加元素                          |
                | boolean remove(Object o)   | 从集合中移除指定的元素              |
                | boolean removeIf(Object o) | 根据条件进行移除                   |
                | void   clear()             | 清空集合中的元素                   |
                | boolean contains(Object o) | 判断集合中是否存在指定的元素         |
                | boolean isEmpty()          | 判断集合是否为空                   |
                | int   size()               | 集合的长度,也就是集合中元素的个数    |
    
                //注意点:
                    Collection是一个接口,我们不能直接创建他的对象
                    所以,现在我们学习他的方法时,只能创建他实现类的对象
                    实现类:ArrayList
             */
    
            //目的:为了学习Collection接口里面的方法
            //自己在做一些练习的时候,还是按照之前的方式去创建对象
            Collection<String> coll = new ArrayList<>();
    
            //1.添加元素
            //细节1:如果我们要往List系列集合中添加数据,那么永远返回true,因为List系列时允许元素重复的
            //细节2: 如果我们要往Set系列集合中添加数据,如果当前要添加的元素不存在,方法返回true,表示添加成功
            //                                      如果当前要添加的元素已经存在,方法返回false,表示添加失败
            //                                      因为Set系列的集合不允许重复元素
            coll.add("aaa");
            coll.add("a.txt");
            coll.add("aaa");
            coll.add("ccc");
    
            System.out.println(coll);
    
            //2.清空
            //coll.clear();
            //System.out.println(coll);
    
            //3.删除
            //细节1:因为Collection里面定义的是共性的方法,所以此时不能通过索引进行删除。只能通过元素的对象进行删除。
            //细节2:方法会有一个布尔类型的返回值,删除成功返回true,删除失败返回false
            //如果要删除的元素不存在,就会删除失败
            coll.remove("aaa");
            System.out.println(coll);
    
            //4.判断元素是否包含
            //细节:底层是依赖equals方法进行判断是否存在的
            //所以,如果集合中存储的是自定义对象,也想通过contains方法来判断是否包含,那么在javabean类中,一定要重写equals方法。
            boolean result = coll.contains("aaa");
            System.out.println(result);
    
            //5.判断集合是否非空
            boolean result2 = coll.isEmpty();
            System.out.println(result2);
    
            //6.获取集合的长度
            System.out.println(coll.size());
        }
    }
    
    /*添加应用数据类型*/
    
    /*
    Student.java
    */
    import java.util.Objects;
    
    public class Student {
        private String name;
        private int age;
    
    
        public Student() {
        }
    
        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        /**
         * 获取
         * @return name
         */
        public String getName() {
            return name;
        }
    
        /**
         * 设置
         * @param name
         */
        public void setName(String name) {
            this.name = name;
        }
    
        /**
         * 获取
         * @return age
         */
        public int getAge() {
            return age;
        }
    
        /**
         * 设置
         * @param age
         */
        public void setAge(int age) {
            this.age = age;
        }
    
        public String toString() {
            return "Student{name = " + name + ", age = " + age + "}";
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Student student = (Student) o;
            return age == student.age && Objects.equals(name, student.name);
        }
    
    }
    
    /*
    Test.java
    */
    import java.util.ArrayList;
    import java.util.Collection;
    
    public class Test {
        public static void main(String[] args) {
            //1.创建集合的对象
            Collection<Student> coll = new ArrayList<>();
    
            //2.把学生对象添加到集合当中
            coll.add(new Student("zhangsan", 23));
            coll.add(new Student("lisi", 24));
            coll.add(new Student("wangwu", 25));
    
            //3.判断集合中某一个对象是否包含
            Student s = new Student("zhangsan", 23);
    
            //因为contains方法在底层依赖equals方法判断对象是否一致的
            //如果存的是自定义对象,没有重写equals方法,那么默认使用Object类中的equals方法进行判断,
            //而Object类中的equals方法,依赖地址值进行判断
            //如果同姓名同年龄,就认为是同一个学生
            //所以,需要在自定义的Javabean类中,重写equals方法就可以了
            System.out.println(coll.contains(s));
      
        }
    }
    

2.3 遍历方法【3种】

  • Collection迭代器有三种遍历方式:
    • 方式1:迭代器遍历
    • 方式2:forEach方法+Lambda表达式
    • 方式3:增强for,其底层就是使用迭代器进行遍历
  • 方式1和方式2主要实现了迭代接口Iterable
    方法说明
    Iterator<T> iterator()返回此集合中元素的迭代器
    void forEach(Consumer<? super T> action)对迭代的每个元素执行给定操作,直到处理迭代所有元素或操作引发异常

2.3.1 迭代器遍历

  • 迭代器在Java中的类是Iterator,迭代器是集合专用的遍历方式

  • Iterator类中的常用成员方法

    方法说明
    boolean hasNext()判断当前位置是否有元素可以取出,有元素返回true,没有元素返回false
    E next()获取当前位置的元素,并将迭代器对象移向下一个位置
    void remove()删除迭代器对象当前指向的元素
  • Collection集合获取迭代器的成员方法

    方法说明
    Iterator<E> iterator()返回此集合中元素的迭代器,通过集合对象的iterator()方法得到,默认指向当前集合的0索引
    该方法是实现了其迭代接口Iterable中的方法
  • 迭代器遍历的实现步骤:

    1. 获取迭代器对象
    2. 判断迭代器指针指向的位置是否有元素
    3. 如果有就获取该元素,并将迭代器指向下一个元素
  • 代码演示:

    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Iterator;
    
    public class Test {
        public static void main(String[] args) {
            /*
                Collection系列集合三种通用的遍历方式:
                    1.迭代器遍历
                    2.增强for遍历
                    3.lambda表达式遍历
    
                迭代器遍历相关的三个方法:
                        Interator<E> iterator() :获取下一个迭代器对象
                        boolean hasNext()       :判断当前指向的位置是否有元素
                        E next()                :获取当前指向的元素并移动指针
             */
    
            //1.创建集合并添加元素
            Collection<String> coll = new ArrayList<>();
            coll.add("aaa");
            coll.add("a.txt");
            coll.add("ccc");
            coll.add("ddd");
    
            //2.获取迭代器对象
            //迭代器就好比是一个箭头,默认指向集合的0索引
            Iterator<String> it = coll.iterator();
    
            //3.利用循环不断的去获取集合中每一个元素
            while (it.hasNext()) {
                //4.next方法的两件事情:获取元素并移动指针
                String str = it.next();
                System.out.println(str);
            }
        }
    }
    
  • 迭代器中删除的方法

    public class IteratorDemo2 {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>();
            list.add("a");
            list.add("b");
            list.add("b");
            list.add("c");
            list.add("d");
    
            Iterator<String> it = list.iterator();
            while(it.hasNext()){
                String s = it.next();
                if("b".equals(s)){
                    //指向谁,那么此时就删除谁.
                    it.remove();
                }
            }
            System.out.println(list);
        }
    }
    
  • 注意细节:
    1.报错NoSuchElementException:迭代器的指针指向了最后没有元素的位置
    2.迭代器遍历完毕,指针不会复位
    3.循环中只能用一次next()方法
    4.迭代器遍历时,不能用集合的方法进行增加或者删除

    public class A04_CollectionDemo4 {
        public static void main(String[] args) {
          /*
            迭代器的细节注意点:
                1.报错NoSuchElementException
                2.迭代器遍历完毕,指针不会复位
                3.循环中只能用一次next方法
                4.迭代器遍历时,不能用集合的方法进行增加或者删除
                	暂时当做一个结论先行记忆,在今天我们会讲解源码详细的再来分析。
                    如果我实在要删除:那么可以用迭代器提供的remove方法进行删除。
                    如果我要添加,暂时没有办法。(只是暂时)
           */
    
            //1.创建集合并添加元素
            Collection<String> coll = new ArrayList<>();
            coll.add("aaa");
            coll.add("bbb");
            coll.add("ccc");
            coll.add("ddd");
    
            //2.获取迭代器对象
            //迭代器就好比是一个箭头,默认指向集合的0索引处
            Iterator<String> it = coll.iterator();
            //3.利用循环不断的去获取集合中的每一个元素
            while(it.hasNext()){
                //4.next方法的两件事情:获取元素并移动指针
                String str = it.next();
                System.out.println(str);
            }
    
            //当上面循环结束之后,迭代器的指针已经指向了最后没有元素的位置
            //System.out.println(it.next());//NoSuchElementException
    
            //迭代器遍历完毕,指针不会复位
            System.out.println(it.hasNext());
    
            //如果我们要继续第二次遍历集合,只能再次获取一个新的迭代器对象
            Iterator<String> it2 = coll.iterator();
            while(it2.hasNext()){
                String str = it2.next();
                //迭代器遍历时,不能用集合的方法进行增加或者删除
                if("bbb".equals(str)){
                    //coll.remove("bbb");
                    it2.remove();
                }
                System.out.println(str);
            }
        }
    }
    

2.3.2 forEach方法 + lambda表达式

  • forEach方法:该方法是实现了迭代接口Iterable接口后继承的方法

    方法说明
    void forEach(Consumer<? super T> action)Iterable每个元素执行给定操作,直到处理 Iterable所有元素或操作引发异常。
  • 代码演示:

    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.function.Consumer;
    
    public class Test {
        public static void main(String[] args) {
            /*
    
                Collection系列集合三种通用的遍历方式:
                    1.迭代器遍历
                    2.增强for遍历
                    3.lambda表达式遍历
    
                lambda表达式遍历:
                    default void forEach(Consumer<? super T> action):
             */
    
            //1.创建集合并添加元素
            Collection<String> coll = new ArrayList<>();
            coll.add("aaa");
            coll.add("a.txt");
            coll.add("ccc");
            coll.add("ddd");
    
            //2.利用匿名内部类的形式
            //底层原理:
            //其实也会自己遍历集合,依次得到每一个元素
            //把得到的每一个元素,传递给下面的accept方法
            //s依次表示集合中的每一个元素
            coll.forEach(new Consumer<String>() {
                @Override
                public void accept(String s) {
                    System.out.println(s);
                }
            });
    
            //lambda表达式
            coll.forEach(s -> System.out.println(s));
        }
    }
    

2.3.3 增强for

  • 介绍:

    • 增强for循环是为了简化迭代器代码而出现的,其底层实际上是使用迭代器进行遍历。
    • JDK5引入了增强for循环,其内部机制是基于Iterator迭代器实现的。
    • 只有实现了Iterable接口的类才能够使用增强for循环和迭代器,包括单列集合和数组。
    • 增强for循环大大简化了数组和集合的遍历代码,使得代码更加简洁和易读。
  • 格式

    for(集合/数组中元素的数据类型 变量名 :  集合/数组名) {
    	// 已经将当前遍历到的元素封装到变量中了,直接使用变量即可
    }
    
  • 代码

    public class MyCollectonDemo1 {
        public static void main(String[] args) {
            //1.创建集合并添加元素
            Collection<String> coll = new ArrayList<>();
            coll.add("aaa");
            coll.add("bbb");
            coll.add("ccc");
            coll.add("ddd");
    
            //2.利用增强for进行遍历
            //1.数据类型一定是集合或者数组中元素的类型
            //2.str仅仅是一个变量名而已,在循环的过程中,依次表示集合或者数组中的每一个元素
            //3.coll就是要遍历的集合或者数组
            for (String str : coll) {
                System.out.println(s);
            }
        }
    }
    
  • 细节点注意:修改增强for中的变量,不会改变集合中原本的数据

    for(String str : coll){
        str = "q";
    }
    

2.4 迭代器源码分析

  • 迭代器遍历相关的三个方法:
    • Iterator<E> iterator() :获取一个迭代器对象
    • boolean hasNext():判断当前指向的位置是否有元素
    • E next() :获取当前指向的元素并移动指针
  • 图解:
    在这里插入图片描述

2.List集合

2.1 概述

  • List集合的概述
    • 有序集合,这里的有序指的是存取顺序
    • 用户可以精确控制列表中每个元素的插入位置,用户可以通过整数索引访问元素,并搜索列表中的元素
    • 与Set集合不同,列表通常允许重复的元素
    • 常用的实现类有:ArrayList、LinkedList、Vector
  • List集合的特点:简记:存取有序、可重复、有索引
    • 集合中的元素允许重复
    • 集合中的元素是有顺序的,各元素插入的顺序就是各元素的顺序
    • 集合中的元素可以通过索引来访问或者设置

2.2 List集合特有方法

  • 特有方法:

    方法名描述
    void add(int index, E element)在此集合中的指定位置插入指定的元素
    E remove(int index)删除指定索引处的元素,并返回被删除的元素
    E set(int index, E element)修改指定索引处的元素,并返回被修改的元素
    E get(int index)返回指定索引处的元素
    default void sort(Comparator<? super E> c)根据指定规则对列表进行排序
    List<E> subList(int fromIndex, int toIndex)返回列表指定的指定索引区间的子列表,包头不包尾
  • 示例代码

    import java.util.ArrayList;
    import java.util.List;
    
    public class Test {
        public static void main(String[] args) {
            /*
                | void add(int index,E   element) | 在此集合中的指定位置插入指定的元素     |
                | E remove(int   index)           | 删除指定索引处的元素,返回被删除的元素 |
                | E set(int index,E   element)    | 修改指定索引处的元素,返回被修改的元素 |
                | E get(int   index)              | 返回指定索引处的元素                   |
             */
    
            //1.创建一个集合
            List<String> list = new ArrayList<>();
    
            //2.添加元素
            list.add("aaa");
            list.add("a.txt");
            list.add("ccc");
            list.add("ddd");
    
            //| void add(int index,E   element) | 在此集合中的指定位置插入指定的元素     |
            //细节:原来索引上的元素会依次往后移
            list.add(1, "QQQ");
    
            //| E remove(int   index)           | 删除指定索引处的元素,返回被删除的元素 |
            String remove = list.remove(0);
            System.out.println(remove);
    
            //| E set(int index,E   element)    | 修改指定索引处的元素,返回被修改的元素 |
            String result = list.set(0, "QQQ");
            System.out.println(result);
    
            //| E get(int   index)              | 返回指定索引处的元素                   |
            String s = list.get(0);
            System.out.println(s);
    
    
            //3.打印集合
            System.out.println(list);
        }
    }
    

2.3 List遍历方式【5种】

  • List的五种遍历方式及其选择策略:

    • 迭代器遍历:在遍历过程中需要删除元素,请使用迭代器遍历
    • 列表迭代器:在遍历过程中需要添加元素,请使用列表迭代器
    • 增强for遍历:仅仅想遍历,那么使用增强for或者Lambda表达式
    • forEach方法 + Lambda表达式:
    • 普通for:如果遍历的时候想操作索引,可以用普通for
  • 代码示例:

    //创建集合并添加元素
    List<String> list = new ArrayList<>();
    list.add("aaa");
    list.add("bbb");
    list.add("ccc");
    
    //1.迭代器
    /*Iterator<String> it = list.iterator();
         while(it.hasNext()){
            String str = it.next();
            System.out.println(str);
    }*/
    
    
    //2.增强for
    //下面的变量s,其实就是一个第三方的变量而已。
    //在循环的过程中,依次表示集合中的每一个元素
    /* for (String s : list) {
           System.out.println(s);
       }*/
    
    //3.Lambda表达式
    //forEach方法的底层其实就是一个循环遍历,依次得到集合中的每一个元素
    //并把每一个元素传递给下面的accept方法
    //accept方法的形参s,依次表示集合中的每一个元素
    //list.forEach(s->System.out.println(s) );
    
    
    //4.普通for循环
    //size方法跟get方法还有循环结合的方式,利用索引获取到集合中的每一个元素
    /*for (int i = 0; i < list.size(); i++) {
                //i:依次表示集合中的每一个索引
                String s = list.get(i);
                System.out.println(s);
            }*/
    
    // 5.列表迭代器
    //获取一个列表迭代器的对象,里面的指针默认也是指向0索引的
    
    //额外添加了一个方法:在遍历的过程中,可以添加元素
    ListIterator<String> it = list.listIterator();
    while(it.hasNext()){
        String str = it.next();
        if("bbb".equals(str)){
            //qqq
            it.add("qqq");
        }
    }
    System.out.println(list);
    

2.4 细节点注意:

  • List集合两种删除方式
    1. 直接删除指定元素
    2. 通过索引进行删除
  • 注意细节:当List集合种的元素是Integer类型的时候,使用remove(i)方法,此时remove方法会执行会删除指定元素i的操作,即remove方法不会自动装箱
    • 在调用方法的时候,如果出现了重载现象,优先调用实参跟形参类型一致的那个方法

代码示例:

import java.util.ArrayList;
import java.util.List;

public class Test {
    public static void main(String[] args) {
        //List系列集合中的两个删除的方法
        //1.直接删除元素
        //2.通过索引进行删除

        //1.创建集合并添加元素
        List<Integer> list = new ArrayList<>();

        list.add(1);
        list.add(2);
        list.add(3);

        //2.删除元素
        //请问:此时删除的是1这个元素,还是1索引上的元素?
        //为什么?
        //因为在调用方法的时候,如果出现了重载现象
        //优先调用,实参跟形参类型一致的那个方法
        //即remove方法不会自动装箱
        list.remove(1);

        //手动装箱,手动把基本数据类型的1,变成Integer类型
        Integer i = Integer.valueOf(2);
        list.remove(i);

        System.out.println(list);
    }
}

3.ArrayList集合

3.1 概述

  • 特点:
    • 长度可以变化,只能存储引用数据类型
    • 底层是数组结构实现,查询快、增删慢
  • 泛型的使用:用于约束集合中存储元素的数据类型
  • 集合和数组的优势对比:
    • 长度可变
    • 添加数据的时候不需要考虑索引,默认将数据添加到末尾
  • 类间关系图
    image-20240514201916139

3.2 ArrayList类常用方法

3.2.1 构造方法

方法名说明
public ArrayList()创建一个空的集合对象

3.2.2 成员方法

方法说明
public boolean add(要添加的元素)将指定的元素追加到此集合的末尾
public boolean remove(要删除的元素)删除指定元素,返回值表示是否删除成功
public E remove(int index)删除指定索引处的元素,返回被删除的元素
public E set(int index, E element)修改指定索引处的元素,返回被修改的元素
public E get(int index)返回指定索引处的元素
public int size()返回集合中的元素的个数

3.2.3 示例代码

public class ArrayListDemo02 {
    public static void main(String[] args) {
        //创建集合
        ArrayList<String> array = new ArrayList<String>();

        //添加元素
        array.add("hello");
        array.add("world");
        array.add("java");

        //public boolean remove(Object o):删除指定的元素,返回删除是否成功
        //        System.out.println(array.remove("world"));
        //        System.out.println(array.remove("javaee"));

        //public E remove(int index):删除指定索引处的元素,返回被删除的元素
        //        System.out.println(array.remove(1));

        //IndexOutOfBoundsException
        //        System.out.println(array.remove(3));

        //public E set(int index,E element):修改指定索引处的元素,返回被修改的元素
        //        System.out.println(array.set(1,"javaee"));

        //IndexOutOfBoundsException
        //        System.out.println(array.set(3,"javaee"));

        //public E get(int index):返回指定索引处的元素
        //        System.out.println(array.get(0));
        //        System.out.println(array.get(1));
        //        System.out.println(array.get(2));
        //System.out.println(array.get(3)); //?????? 自己测试

        //public int size():返回集合中的元素的个数
        System.out.println(array.size());

        //输出集合
        System.out.println("array:" + array);
    }
}

3.3 ArrayList遍历

3.3.1 遍历字符串

public class ArrayListDemo3 {
    public static void main(String[] args) {
        /*
    		创建一个存储字符串的集合,存储3个字符串元素,使用程序实现在控制台遍历该集合
    	*/
        //1.创建集合对象
        ArrayList<String> list = new ArrayList<>();

        //2.添加元素
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        list.add("ddd");

        //3.遍历
        //快捷键: 
        //list.fori 正向遍历
        //list.forr 倒着遍历
        System.out.print("[");
        for (int i = 0; i < list.size(); i++) {
            //i 依次表示集合里面的每一个索引

            if(i == list.size() - 1){
                //最大索引
                System.out.print(list.get(i));
            }else{
                //非最大索引
                System.out.print(list.get(i) + ", ");
            }
        }
        System.out.print("]");
    }
}

3.3.2 遍历基本数据类型

  • 注意细节:集合中只能存储引用数据类型,因此在指定指定集合中添加基本数据类型的时候,需要将基本数据类型变成相应的包装类
    • int - - > Integer
    • char - - > Character
public class ArrayListDemo4 {
    public static void main(String[] args) {
        //1.创建集合
        ArrayList<Integer> list = new ArrayList<>();

        //2.添加元素
        //jdk5之后 int Integer 之间是可以相互转化的
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);

        //3.遍历集合
        System.out.print("[");
        for (int i = 0; i < list.size(); i++) {
            if (i == list.size() - 1) {
                System.out.print(list.get(i));
            } else {
                System.out.print(list.get(i) + ",");
            }
        }
        System.out.println("]");
    }
}

3.3.3 遍历引用数据类型

public class ArrayListDemo4 {
    public static void main(String[] args) {
        /*
    		创建一个存储学生对象的集合,存储3个学生对象,使用程序实现在控制台遍历该集合
    	*/
        
        //1.创建集合对象,用来存储数据
        ArrayList<Student> list = new ArrayList<>();

        //2.创建学生对象
        Student s1 = new Student("zhangsan",16);
        Student s2 = new Student("lisi",15);
        Student s3 = new Student("wangwu",18);

        //3.把学生对象添加到集合中
        list.add(s1);
        list.add(s2);
        list.add(s3);

        //4.遍历
        for (int i = 0; i < list.size(); i++) {
            //i 依次表示集合中的每一个索引
            Student stu = list.get(i);
            System.out.println(stu.getName() + ", " + stu.getAge());
        }
    }
}

3.4 案例:查找用户的索引

public class ArrayListDemo6 {
    public static void main(String[] args) {
        /*
            需求:
                1,main方法中定义一个集合,存入三个用户对象。
                    用户属性为:id,username,password
                2,要求:定义一个方法,根据id查找对应的学生信息。
                        如果存在,返回索引
                        如果不存在,返回-1
        */


        //1.创建集合对象
        ArrayList<User> list = new ArrayList<>();

        //2.创建用户对象
        User u1 = new User("heima001", "zhangsan", "123456");
        User u2 = new User("heima002", "lisi", "1234");
        User u3 = new User("heima003", "wangwu", "1234qwer");

        //3.把用户对象添加到集合当中
        list.add(u1);
        list.add(u2);
        list.add(u3);

        //4.调用方法,通过id获取对应的索引
        int index = getIndex(list, "heima001");

        System.out.println(index);

    }


    //1.我要干嘛?  根据id查找对应的学生信息
    //2.我干这件事情需要什么才能完成?   集合 id
    //3.方法的调用处是否需要继续使用方法的结果?
    //要用必须返回,不要用可以返回也可以不返回
    //明确说明需要有返回值 int
    public static int getIndex(ArrayList<User> list, String id) {
        //遍历集合得到每一个元素
        for (int i = 0; i < list.size(); i++) {
            User u = list.get(i);
            String uid = u.getId();
            if(uid.equals(id)){
                return i;
            }
        }
        //因为只有当集合里面所有的元素都比较完了,才能断定id是不存在的。
        return -1;
    }
}

3.5 案例:判断用户的是否存在

public class ArrayListDemo5 {
    public static void main(String[] args) {
       /* 
       	需求:
            1,main方法中定义一个集合,存入三个用户对象。
            		用户属性为:id,username,password
            2,要求:定义一个方法,根据id查找对应的学生信息。
                    如果存在,返回true
                    如果不存在,返回false
        */

        //1.定义集合
        ArrayList<User> list = new ArrayList<>();

        //2.创建对象
        User u1 = new User("heima001","zhangsan","123456");
        User u2 = new User("heima002","lisi","12345678");
        User u3 = new User("heima003","wangwu","1234qwer");

        //3.把用户对象添加到集合当中
        list.add(u1);
        list.add(u2);
        list.add(u3);

        //4.调用方法,查询id是否存在
        boolean result = contains(list, "heima001");
        System.out.println(result);

    }

    //定义在测试类中的方法需要加static
    //1.我要干嘛? 我要根据id查询学生是否存在
    //2.我干这件事情,需要什么才能完成? 集合 id
    //3.方法的调用处是否需要使用方法的结果?
    //如果要用,必须返回,如果不用,可以返回也可以不返回
    //但是本题明确说明需要返回
    public static boolean contains(ArrayList<User> list, String id){
        //循环遍历集合,得到集合里面的每一个元素
        //再进行判断

        for (int i = 0; i < list.size(); i++) {
            //i 索引  list.get(i); 元素
            User u = list.get(i);
            //判断id是否存在,我是拿着谁跟谁比较
            //需要把用户对象里面的id拿出来再进行比较。
            String uid = u.getId();
            if(id.equals(uid)){
                return true;//return 关键字:作用就是结束方法。
            }
        }
        //只有当集合里面所有的元素全部比较完毕才能认为是不存在的。
        return false;
    }

}

3.6 ArrayList源码分析

扩容机制

核心步骤:

  1. 创建ArrayList对象的时候,虚拟机会在底层利用空参构造创建了一个默认长度为0的数组
    • ArrayList类的两个重要成员变量
      • elementDate:数组名
      • size:数组大小,这个变量有两层含义
        ①:元素的个数,也就是集合的长度
        ②:下一个元素的存入位置
        image-20240517180355026
  2. 添加第一个元素时,底层会创建一个新的长度为10的数组,添加完毕后,size++
    • 图解:
      在这里插入图片描述

扩容时机一:
3. 当存满时候,会创建一个新的数组,新数组的长度,是原来的1.5倍,也就是长度为15;再把所有的元素,全拷贝到新数组中;如果继续添加数据,这个长度为15的数组也满了,那么下次还会继续扩容,还是1.5倍
扩容时机二:
4. 一次性添加多个数据,扩容1.5倍不够,怎么办呀?:如果一次添加多个元素,1.5倍放不下,那么新创建数组的长度以实际为准。

举个例子:在一开始,如果默认的长度为10的数组已经装满了,在装满的情况下,我一次性要添加100个数据很显然,10扩容1.5倍,变成15,还是不够,怎么办?:此时新数组的长度,就以实际情况为准,就是110

  1. 添加多个元素图解:
    在这里插入图片描述

4.Queue与Deque【队列与双端队列】

4.1 Queue:队列

4.1.1 概述

  • 队列通常(但不一定)以FIFO(先进先出)方式对元素进行排序,其中的例外是优先级队列,它根据提供的比较器对元素进行排序,或者元素的自然顺序,以及LIFO队列(或堆栈),它们对元素LIFO(后进先出)进行排序。 无论使用什么顺序,队列的头部是通过调用remove()poll()删除的元素。
  • 在FIFO队列中,所有新元素都插入队列的尾部

4.1.2 方法摘要

方法说明
boolean add(E e)将指定的元素插入此队列
E element()检索但不删除此队列的头部。
boolean offer(E e)入队
E peek()检索但不删除此队列的头部
E poll()出队
E remove()检索并删除此队列的头部

4.2 Deque:双端队列

4.2.1 概述

  • 此接口扩展了Queue接口,当deque用作队列时,会产生FIFO(先进先出)行为,元素在双端队列的末尾添加并从头开始删除, 继承自Queue接口的方法与Deque方法完全等效
  • Deques也可以用作LIFO(后进先出)堆栈,应优先使用此接口,而不是旧版Stack, 当deque用作堆栈时,元素将从双端队列的开头推出并弹出, 堆栈方法相当于Deque方法,
  • deque用作队列或堆栈时, peek方法同样有效;在任何一种情况下,元素都是从双端队列的开头绘制的

4.2.2 方法摘要

方法说明
int size()返回此双端队列中的元素数
void push(E e)从此双端队列表示的堆栈中压入一个元素
E pop()从此双端队列表示的堆栈中弹出一个元素
boolean contains(Object o)如果此双端队列包含指定的元素,则返回 true
boolean addAll(Collection<? extends E> c)在此双端队列的末尾添加指定集合中的所有元素
boolean offerFirst(E e)将指定元素插入此双端队列的前面,除非它违反容量限制
boolean offerLast(E e)在此双端队列的末尾插入指定的元素,除非它违反容量限制
E pollLast()检索并删除此双端队列的最后一个元素,如果此双端队列为空,则返回 null
E pollFirst()检索并删除此双端队列的第一个元素,如果此双端队列为空,则返回 null
E peekFirst()检索但不删除此双端队列的第一个元素,如果此双端队列为空,则返回 null
E peekLast()检索但不删除此双端队列的最后一个元素,如果此双端队列为空,则返回 null
E getFirst()检索但不删除此双端队列的第一个元素
E getLast()检索但不删除此双端队列的最后一个元素
boolean remove(Object o)从此双端队列中删除第一次出现的指定元素
E removeFirst()检索并删除此双端队列的第一个元素
boolean removeFirstOccurrence(Object o)从此双端队列中删除第一次出现的指定元素
E removeLast()检索并删除此双端队列的最后一个元素
boolean removeLastOccurrence(Object o)从此双端队列中删除最后一次出现的指定元素

5.LinkedList集合

5.1 概述

  • LinkedList 继承了 AbstractSequentialList 类,同时实现了List、Queue、Deque接口,可进行列表的相关操作
  • LinkedList 同时还可以作为队列使用,进行队列相关的操作(这个特征在使用Java语言刷LeetCode的时候有大用)
  • LinkedList 实现了 Cloneable 接口,可实现克隆
  • LinkedList 实现了 java.io.Serializable 接口,即可支持序列化,能通过序列化去传输
  • LinkedList底层是双链表数据结构实现,优点是适合动态插入和删除元素,缺点是随机查找和遍历速度比较慢
    img

5.2 特有方法

  • 特有方法:

    方法名说明
    public void addFirst(E e)在该列表开头插入指定的元素
    public void addLast(E e)将指定的元素追加到此列表的末尾
    public E getFirst()返回此列表中的第一个元素
    public E getLast()返回此列表中的最后一个元素
    public E removeFirst()从此列表中删除并返回第一个元素
    public E removeLast()从此列表中删除并返回最后一个元素
    public boolean offer(E e)向链表末尾添加元素,返回是否成功,成功为 true,失败为 false。
    public boolean offerFirst(E e)头部插入元素,返回是否成功,成功为 true,失败为 false。
    public boolean offerLast(E e)尾部插入元素,返回是否成功,成功为 true,失败为 false。
  • 示例代码

    public class MyLinkedListDemo4 {
        public static void main(String[] args) {
            LinkedList<String> list = new LinkedList<>();
            list.add("aaa");
            list.add("bbb");
            list.add("ccc");
    //        public void addFirst(E e)	在该列表开头插入指定的元素
            //method1(list);
    
    //        public void addLast(E e)	将指定的元素追加到此列表的末尾
            //method2(list);
    
    //        public E getFirst()		返回此列表中的第一个元素
    //        public E getLast()		返回此列表中的最后一个元素
            //method3(list);
    
    //        public E removeFirst()		从此列表中删除并返回第一个元素
    //        public E removeLast()		从此列表中删除并返回最后一个元素
            //method4(list);
          
        }
    
        private static void method4(LinkedList<String> list) {
            String first = list.removeFirst();
            System.out.println(first);
    
            String last = list.removeLast();
            System.out.println(last);
    
            System.out.println(list);
        }
    
        private static void method3(LinkedList<String> list) {
            String first = list.getFirst();
            String last = list.getLast();
            System.out.println(first);
            System.out.println(last);
        }
    
        private static void method2(LinkedList<String> list) {
            list.addLast("www");
            System.out.println(list);
        }
    
        private static void method1(LinkedList<String> list) {
            list.addFirst("qqq");
            System.out.println(list);
        }
    }
    

5.3 LinkedList源码分析

  • 核心步骤如下:
    1. 刚开始创建的时候,底层创建了两个变量:一个记录头结点first,一个记录尾结点last,默认为null
    2. 添加第一个元素时,底层创建一个结点对象,first和last都记录这个结点的地址值
    3. 添加第二个元素时,底层创建一个结点对象,第一个结点会记录第二个结点的地址值,last会记录新结点的地址值
  • 图解:
    LinkedList源码分析

6. Vector与Stack

6.1 Vector

Vector也是List接口的实现类,内部也是通过数组来实现。
Vector类的代码声明如下所示:

public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
}

与ArrayList不同的是,Vector是线程安全的,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性。不过这也造成Vector的缺点:实现线程的同步需要额外的花费,因此它的访问速度会比ArrayList慢一些。
可以认为Vector是ArrayList在多线程环境下的实现版本。
所以Vector类的使用方法和ArrayList基本一样,只需修改下声明处的代码即可:

List<String> platformList = new Vector<>();

由于要支持线程同步,因此Vector类的很多方法都有synchronized关键字,如下所示:

public synchronized boolean isEmpty() {
    return elementCount == 0;
}

public synchronized int size() {
    return elementCount;
}

public synchronized void addElement(E obj) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = obj;
}

6.2 Stack

6.2.1 概述

  • Stack类表示后进先出(LIFO)对象堆栈。 它通过五个操作扩展了类Vector ,允许将向量视为堆栈。 提供了通常的pushpop操作,以及在堆栈顶部项目中的peek的方法,用于测试堆栈是否为empty的方法,以及用于项目的堆栈的方法以及发现它的距离search是从顶部。
  • Deque接口及其实现提供了更完整和一致的LIFO堆栈操作集,应优先使用此类
    Deque<Integer> stack = new ArrayDeque<Integer>(); 
    

6.2.2 方法摘要

构造方法
构造器描述
Stack()创建一个空堆栈
成员方法摘要
方法描述
boolean empty()测试此堆栈是否为空
E peek()查看此堆栈顶部的对象,而不将其从堆栈中删除
E pop()移除此堆栈顶部的对象,并将该对象作为此函数的值返回
E push(E item)将项目推到此堆栈的顶部
int search(Object o)返回对象在此堆栈上的从1开始的位置

7.ArrayList、LinkedList、Vector的区别

7.1 相同点

ArrayList、LinkedList、Vector都实现了List接口,所以使用方式很类似,通过上面的示例也能发现这一点。

7.2 不同点

但是ArrayList、LinkedList、Vector的内部实现方式不同,也就导致了它们之间是有区别的。

7.2.1 存储结构

ArrayList和Vector是基于数组实现的,LinkedList是基于双向链表实现的。
这也就导致ArrayList适合随机查找和遍历,而LinkedList适合动态插入和删除元素。
关于数组和双向链表,这里不做详解,后续会单独写篇文章总结。

7.2.2 线程安全性

ArrayList和LinkedList是线程不安全的,Vector是线程安全的。
Vector可以看做是ArrayList在多线程环境下的另一种实现方式,这也导致了Vector的效率没有ArraykList和LinkedList高。
如果要在并发环境下使用ArrayList或者LinkedList,可以调用Collections类的synchronizedList()方法:

Collections.synchronizedList(platformList);

7.2.3 扩容机制

ArrayList和Vector都是使用Object类型的数组来存储数据的,ArrayList的默认容量是0,Vector的默认容量是10。
空说无凭,我们先看下ArrayList的使用示例:

List<String> strArrayList = new ArrayList<>();

for (int i = 0; i < 20; i++) {
    strArrayList.add(String.valueOf(i));
}

执行的ArrayList构造函数的源码为:

transient Object[] elementData;

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

再看下Vector的使用示例:

List<String> strVector = new Vector<>();

for (int i = 0; i < 30; i++) {
    strVector.add(String.valueOf(i));
}

执行的Vector构造函数的源码为:

protected Object[] elementData;
protected int capacityIncrement;

public Vector() {
    this(10);
}

public Vector(int initialCapacity) {
    this(initialCapacity, 0);
}

public Vector(int initialCapacity, int capacityIncrement) {
      super();
      if (initialCapacity < 0)
          throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
      this.elementData = new Object[initialCapacity];
      this.capacityIncrement = capacityIncrement;
}

当向这两种类型中添加元素时,若容量不够,就会进行扩容,扩容的本质是产生一个新数组,将原数组的数据复制到新数组,再将新的元素添加到新数组中,使用的方法是Arrays.copyOf(),其中ArrayList扩容后的容量是之前的1.5倍,Vector默认情况下扩容后的容量是之前的2倍

仍然使用上面的ArrayList的例子:

List<String> strArrayList = new ArrayList<>();

for (int i = 0; i < 20; i++) {
    strArrayList.add(String.valueOf(i));
}

在执行完List<String> strArrayList = new ArrayList<>();后,此时strArrayList的容量是0,

然后在添加第1个元素时,strArrayList的容量会扩容为容量10,

当添加第11个元素时,strArrayList的容量会扩容为容量15,

当添加第16个元素时,strArrayList的容量会扩容为容量22,

如果还需要扩容,依次会扩容到33–>49。

看下ArrayList的源码,就明白为什么会这样扩容:

private static final int DEFAULT_CAPACITY = 10;

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
     if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
         return Math.max(DEFAULT_CAPACITY, minCapacity);
     }
     return minCapacity;
}

private void ensureExplicitCapacity(int minCapacity) {
     modCount++;

     // overflow-conscious code
     if (minCapacity - elementData.length > 0)
      	 grow(minCapacity);
}

private void grow(int minCapacity) {
     // overflow-conscious code
     int oldCapacity = elementData.length;
     int newCapacity = oldCapacity + (oldCapacity >> 1);
     if (newCapacity - minCapacity < 0)
         newCapacity = minCapacity;
     if (newCapacity - MAX_ARRAY_SIZE > 0)
         newCapacity = hugeCapacity(minCapacity);
     // minCapacity is usually close to size, so this is a win:
     elementData = Arrays.copyOf(elementData, newCapacity);
}

最核心的代码就是int newCapacity = oldCapacity + (oldCapacity >> 1);,所以ArrayList扩容后的容量是之前的1.5倍。

再看下上面的Vector例子:

List<String> strVector = new Vector<>();

for (int i = 0; i < 30; i++) {
    strVector.add(String.valueOf(i));
}

在执行完List<String> strVector = new Vector<>();后,此时strVector的容量是10,

当添加第11个元素时,strVector的容量会扩容为容量20,

当添加第21个元素时,strVector的容量会扩容为容量40,

如果还需要扩容,依次会扩容到80–>160。

看下Vector的源码,就明白为什么会这样扩容:

public synchronized void addElement(E obj) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = obj;
}

private void ensureCapacityHelper(int minCapacity) {
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private void grow(int minCapacity) {
     // overflow-conscious code
     int oldCapacity = elementData.length;
     int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                      capacityIncrement : oldCapacity);
     if (newCapacity - minCapacity < 0)
         newCapacity = minCapacity;
     if (newCapacity - MAX_ARRAY_SIZE > 0)
         newCapacity = hugeCapacity(minCapacity);
     elementData = Arrays.copyOf(elementData, newCapacity);
}

最核心的代码就是int newCapacity = oldCapacity + ((capacityIncrement > 0) ?capacityIncrement : oldCapacity);,所以Vector默认情况下扩容后的容量是之前的2倍。

7.2.4 效率

ArrayList随机查找和遍历的效率会高一些,但动态插入和删除元素的效率会低一些。

LinkedList动态插入和删除元素的效率会高一些,但随机查找和遍历的效率会低一些。

如果需要在多线程下操作集合元素,建议使用Vector,否则的话,建议使用ArrayList。

码就是int newCapacity = oldCapacity + (oldCapacity >> 1);,所以ArrayList扩容后的容量是之前的1.5倍。

再看下上面的Vector例子:

List<String> strVector = new Vector<>();

for (int i = 0; i < 30; i++) {
    strVector.add(String.valueOf(i));
}

在执行完List<String> strVector = new Vector<>();后,此时strVector的容量是10,

当添加第11个元素时,strVector的容量会扩容为容量20,

当添加第21个元素时,strVector的容量会扩容为容量40,

如果还需要扩容,依次会扩容到80–>160。

看下Vector的源码,就明白为什么会这样扩容:

public synchronized void addElement(E obj) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = obj;
}

private void ensureCapacityHelper(int minCapacity) {
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private void grow(int minCapacity) {
     // overflow-conscious code
     int oldCapacity = elementData.length;
     int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                      capacityIncrement : oldCapacity);
     if (newCapacity - minCapacity < 0)
         newCapacity = minCapacity;
     if (newCapacity - MAX_ARRAY_SIZE > 0)
         newCapacity = hugeCapacity(minCapacity);
     elementData = Arrays.copyOf(elementData, newCapacity);
}

最核心的代码就是int newCapacity = oldCapacity + ((capacityIncrement > 0) ?capacityIncrement : oldCapacity);,所以Vector默认情况下扩容后的容量是之前的2倍。

7.2.4 效率

ArrayList随机查找和遍历的效率会高一些,但动态插入和删除元素的效率会低一些。

LinkedList动态插入和删除元素的效率会高一些,但随机查找和遍历的效率会低一些。

如果需要在多线程下操作集合元素,建议使用Vector,否则的话,建议使用ArrayList。

  • 27
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神马都会亿点点的毛毛张

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值