java基础第十一讲

目录

一、集合

1.1集合的架构

 1.2集合框架的作用

1.3集合框架的特点

二、List

2.1 简易写List方法

2.2ArrayList类

2.3LinkedList

2.4vector

三、泛型

四、set接口

4.1HashSet

4.2TreeSet

五、Map

5.1HashMap

5.2 HashMap创建和使用


一、集合

1.1集合的架构

 1.2集合框架的作用

对象用于封装特有数据,对象多了需要存储,如果对象的个数不确定,数组无法满足。 就使用集合容器进行存储。集合容器因为内部的数据结构不同,有多种具体容器。 不断的向上抽取,就形成了集合框架。 框架的顶层是Collection接口,定义了集合框架中共性的方法。

1.3集合框架的特点

1)集合、数组都是存储对象的容器,但是集合可以存储不同的类型,只要是对象类型就可以存进集合框架中

2)集合中不可以存储基本数据类型

3)集合的长度是可变的。

二、List

2.1 简易写List方法

public class ArrayListTet {
    //创建一个长度为10的对象数组
    private Object[] arrObj = new Object[10];
    //数组长度
    private int size = 0;
    //*************添加*************
    public  void add(Object o){
        //长度为10的数组 最高下标是arr.length-1;
        if(size< arrObj.length){
            arrObj[size]=o;//如果数组没满,将对象添加进该数组
        }else{
            //此时数组的长度不够,需要对数组进行扩容
            int oldLength = arrObj.length;
            int newLength = oldLength + (oldLength >> 1);//向右移一位(二进制)
            //数组扩容时,扩容到大约1.5倍(/2之后,偶数刚好是1.5,奇数只取整数部分,所以会小1)
            Object[] objects = Arrays.copyOf(arrObj,newLength);
            arrObj=objects;
            arrObj[size]=o;//如果数组没满,将对象添加进该数组
        }
        size++;//将size++
    }
    //*****获取数组中内容的长度********
    public int size(){
        return size;
    }
    //*********根据索引取值***************
    //第一种方法
    public Object get (int index){
        Object o = null;
        try{//如果索引大于当前数组的长度,就会产生异常
           o = arrObj[index];
        }catch (ArrayIndexOutOfBoundsException e){
            System.out.println("数组下标越界");
        }finally {
            return o;
        }
    }
    //第二种方法
    /* public Object get(int index){
        Object o = null;
        if (index >= size){//如果索引大于当前数组中内容的长度,就会产生异常
            System.out.println("数组下标越界");
            throw new ArrayIndexOutOfBoundsException();
        }else {
             o = arrObj[index];
             return o;
        }
    }*/
​
    //*********根据元素获取索引**********找到返回索引,找不到返回-1
    public int indexOf(Object o){
        int index = -1;//如果数组中没有该值,返回-1
        for (int i = 0; i < arrObj.length; i++) {
            //equals对于引用数据类型比较的是 内存地址
            if (o.equals(arrObj[i])){//如果arrObj[i]处的元素与o的元素相同
                index = i;
                break;
            }
        }
        return index;
    }
    //*********根据索引删除数据,并返回删除的值**************
    public Object remove(int index){
        Object o = null;
        if (index>0 && index< arrObj.length-1){
            for (int i = index; i < arrObj.length-1; i++) {//从索引处开始,让下一位替代索引位,让下下一位替代下一位
                o = arrObj[index];
                arrObj[i] = arrObj[i+1];//i最大为arrObj.length-2;
            }
            size--;
        }
        return o;
    }
    //************根据元素的值进行删除*********************
    public  void  remove(Object o){
        //获取该值的索引
        int index = indexOf(o);//调用上面的根据元素获取索引
        remove(index);//调用上一个根据索引删除数据
    }
    //**********根据索引进行赋值*************
    public void set(int index , Object o){
        //需要在索引的范围之内进行赋值
        if (index<=arrObj.length-1 && index >=0){
            arrObj[index]=o;
        }
    }
}

2.2ArrayList类

ArrayList要跟Array数组相关。

ArrayList的底层就是数组。

特点:

查找快,插入删除慢(因为会引起其他元素位置改变)

自动扩容(我们只管存取即可 如果长度不够 底层会自动的扩容)

可以存储所有对象(对象类型 或者说是Object的子类 )

可以存储null

存储的数据是有序的 ( 取出的顺序就是存储的顺序 )

数据可以重复

2.2.1 ArrayList的常用方法

法名含义
size()数组中数据的个数并不是长度
add(E e)追加
get(index)根据下标获取值
isEmpty()判断ArrayList中是否有数据
indexOf(数据 )获取aa在ArrayList中首次出现的索引,有返回,没有-1
lastIndexOf(数据)返回数据最后一次出现的索引
contains(数据)判断数据在ArrayList中是否存在
remove(index)/remove(obj)根据下标删除,或者直接删除数据
toArray()将List转换成数组
clear()清空List列表
set(索引,“数据”)将指定索引的数据修改
public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();//最大值是2^31-1
        //add()方法 这里的不是基本数据类型,自动装箱变成包装类
        arrayList.add(12);
        arrayList.add("ab");
        arrayList.add('a');
        arrayList.add('x');
        arrayList.add(124);
        arrayList.add(123);
        //size()
//        System.out.println(arrayList.size());
        //根据索引(下标)取值
//        System.out.println(arrayList.get(1));
        //根据值找索引,可以返回多个相同值的索引
//        System.out.println(arrayList.indexOf('a'));
        //返回值的最后一次出现的索引
//        System.out.println(arrayList.lastIndexOf('a'));
        //根据索引删除该值,并且返回该值的内容
//        System.out.println(arrayList.remove(2));
//        System.out.println(arrayList.size());
//        System.out.println(arrayList.indexOf(2));
        //根据值删除
//        arrayList.remove(124);
        //判断ArrayList中是否有数据
//        boolean empty = arrayList.isEmpty();
//        System.out.println(empty);
        //将List转换成数组
//        Object[] objects = arrayList.toArray();
//        System.out.println(objects.toString());//[Ljava.lang.Object;@1b6d3586
        //清空List列表
//        arrayList.clear();
//        System.out.println(arrayList.size());//0
    }

2.2.2遍历

1) 普通for循环

for (int i = 0; i < list1.size(); i++) {            
    System.out.println(list1.get(i));
}

2)增强for循环

for (Object o : list1) {            
    System.out.println(o);        
}

3)迭代

迭代器(iterator)有时又称光标(cursor)是程序设计的软件设计模式,可在容器对象(container,例如链表或数组上遍访的接口,设计人员无需关心容器对象的内存分配的实现细节。

//        @SuppressWarnings({"all"})//关闭警告
Iterator iterator = arrayList.iterator();
        //hasNext() :如果迭代具有更多元素,则返回 true 。
        while(iterator.hasNext()){//判断是否还有下一个数据
            //next() :返回迭代中的下一个元素。
            Object next = iterator.next();
            System.out.println(next);
        }

扩容原理:

ArrayList其实就是数组,创建的时候给它一个默认的长度10,当需要进行扩容的时候长度是变为原来的1.5倍左右。长度的最大值只能是int的最大值-1。

2.3LinkedList

链表是最简单的动态数据结构,数据存储在节点(Node)中,其节点的数据结构如下:

class Node{
    E e;//数据存储的地方
    Node next;//也是一个节点,他指向当前节点的下一个节点
}

2.3.1链表与数组的对比

链表数组
动态数据结构静态数据结构
无需关心创建的空间是否太大需要在设计时初始化使用的长度
从前往后查找索引的值根据索引直接得到值
有序,查找慢,添加删除快有序,查找快,添加删除慢

2.3.2链表的分类

1)单向链表

单向链表也叫单链表,是链表中最简单的一种形式,它的每个节点包含两个域,一个信息域(元素域)和一个链接域。这个链接指向链表中的下一个节点,而最后一个节点的链接域则指向一个空值。

 

2)单向循环列表

单链表的一个变形是单向循环链表,链表中最后一个节点的next域不再为null,而是指向链表的头节点。

 

3)双向链表

一种更复杂的链表是“双向链表”“双面链表”。每个节点有两个链接:一个指向前一个节点,当此节点为第一个节点时,指向空值;而另一个指向下一个节点,当此节点为最后一个节点时,指向空值。

 

4)双向循环链表

双向循环链表和双向链表的不同在于,第一个节点的pre指向最后一个节点,最后一个节点的next指向第一个节点,也形成一个“环”。而LinkedList就是基于双向循环链表设计的。

 

2.3.3链表的方法使用

这里使用的是LinkedList,LinkedList的底层是双向循环链表,是线程不安全的。

类名LinkedList
成员方法1.public void clear():清空链表 2.publicboolean isEmpty():判断链表是否为空,是返回true,否返回false 3.public int length():获取链表中元素的个数 4.public T get(int i):读取并返回链表中的第i个元素的值 5.public void insert(T t):往链表中添加一个元素; 6.public void insert(int i,T t):在链表的第i个元素之前插入一个值为t的数据元素。 7.public T remove(int i):删除并返回链表中第i个数据元素。 8.public int indexOf(T t):返回链表中首次出现的指定的数据元素的位序号,若不存在,则返回-1
成员内部类private class Node:结点类
成员变量1.private Node head:记录首结点 2.private int N:记录链表的长度
 public static void main(String[] args) {
        //LinkedList是基于双向循环链表的,是线程不安全的。
        LinkedList<Object> linkedList = new LinkedList<>();
        //添加指定元素到链表末尾
        linkedList.add("a");
        linkedList.add("q");
        linkedList.add("w");
        linkedList.add("e");
        linkedList.add("r");
        linkedList.add("t");
        linkedList.add("y");
        //添加指定元素到链表指定位置
        linkedList.add(5,23432151);
        //查询数组长度
        System.out.println("当前链表的长度是:"+linkedList.size());
        //查询索引处的值
        System.out.println("该索引处的值是:"+linkedList.get(2));
        //对列表进行遍历
        for (Object o : linkedList) {
            System.out.println(o);
        }
        //清空链表
        linkedList.clear();
    }

LinkeList由于使用了链表的结构,因此不需要维护容量的大小 , 在List的尾端插入数据与在任意位置插入数据是一样的,不会因为插入的位置靠前而导致插入的方法性能降低。

ArrayList是基于数组实现的,而数组是一块连续的内存空间,如果在数组的任意位置插入元素,必然导致在该位置后的所有元素需要重新排列,因此,其效率相对会比较低。

2.3.4链表的遍历

在JDK1.5之后,至少有3中常用的列表遍历方式:forEach操作,迭代器和for循环。

对于链表,简单for循环的执行时间是很长的,对于链表,应尽量使用foreach和迭代器遍历。

public static void main(String[] args) {
        List list = addArr();
        System.out.println("这是ArrayList");
        timeTest(list);
        List addlink = addlink();
        System.out.println("这是linkedList");
        timeTest(addlink);
    }
    public static  void timeTest(List list){
        long start = System.currentTimeMillis();
        Object x ;
        for (Object o : list) {
            x = o;
        }
        long end = System.currentTimeMillis();
        System.out.println("foreach花费时间"+(end - start)+"ms");
​
        long start1 = System.currentTimeMillis();
        Iterator iterator = list.iterator();
        while (iterator.hasNext()){
            x = iterator.next();
        }
        long end1 = System.currentTimeMillis();
        System.out.println("iterator花费时间"+(end1 - start1)+"ms");
​
        long start2 = System.currentTimeMillis();
        int size = list.size();
        for (int i = 0; i < size; i++) {
            x = list.get(i);
        }
        long end2 = System.currentTimeMillis();
        System.out.println("for花费时间"+(end2 - start2)+"ms");
    }
​
    public static List addlink(){
        LinkedList linkedList = new LinkedList();
        for (int i = 0; i < 100000; i++) {
            linkedList.add(i);
        }
        return linkedList;
    }
    public static List addArr(){
        ArrayList arrayList = new ArrayList();
        arrayList.ensureCapacity(100000);
        for (int i = 0; i < 100000; i++) {
            arrayList.add(i);
        }
        return arrayList;
    }

2.4vector

ArrayList和Vector实现原理都一样,都是基于数组的。使用的是一片连续的内存空间。

vector与ArrayList的区别

vectorArrayList
线程安全,效率低线程不安全,效率高
扩容2倍扩容1.5倍左右

三、泛型

确定存储数据的类型。 列表中只能存储固定的类型。

public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        //限定当前的集合中只能存储String类型的数据;
        list.add("142");
        list.add("142");
        System.out.println(list.get(1));
​
        //集合里面添加Person
        List<Person> people = new ArrayList<>();
        //第一种
        Person person1 = new Person("lwl",18);
        people.add(person1);
        //第二种
        people.add(new Person("lll",23));
        System.out.println(people.get(0).getName());
​
        //集合里面添加List<Person>类型的数据
        List<List<Person>> per = new ArrayList<>();
​
        List<Person> personList1 = new ArrayList<>();
        Person person = new Person("lwl",23);
        personList1.add(person);
​
        per.add(personList1);
        System.out.println(per.get(0).get(0).getName());
    }

四、set接口

HashSet集合存储顺序无序,不可以保存重复元素;

TreeSet是可以保持自然顺序(12数字顺序或abc字母顺序)的,也可以保持定义的比较器比较结果顺序;

LinkedHashSet维护的是一个双重列表,能保持元素添加时的顺序。

4.1HashSet

1)可以存储任意对象类型的数据 包括null。

2)无序(存储时的顺序跟遍历时的顺序不一定一样)

3)数据不能重复(自动去重)

4)数据结构跟List不同,没有下标

5)查找慢,插入删除快。(插入和删除不会引起元素位置改变。红黑二叉树)

4.1.1HashSet方法

返回值类型方法及其描述
booleanadd(E e) 将指定的元素添加到此集合(如果尚未存在)。
voidclear() 从此集合中删除所有元素。
Objectclone() 返回此 HashSet实例的浅层副本:元素本身不被克隆。
booleancontains(Object o) 如果此集合包含指定的元素,则返回 true
booleanisEmpty() 如果此集合不包含元素,则返回 true
Iteratoriterator() 返回此集合中元素的迭代器。
booleanremove(Object o) 如果存在,则从该集合中删除指定的元素。
intsize() 返回此集合中的元素数(其基数)。
Spliteratorspliterator() 在此集合中的元素上创建late-binding故障快速 Spliterator
public void main(String[] args) {
 //实现set接口
        HashSet set = new HashSet();
        //在集合末尾添加指定元素
        set.add("23");
        set.add(453523);
        set.add(634);
        set.add(654);
        set.add(14324321);
        //判断此集合中是否包含指定的元素
//        boolean contains = set.contains("23");
//        System.out.println(contains);
        //获取集合中元素的个数
//        int size = set.size();
//        System.out.println(size);
        //删除数组中指定元素,删除成功返回boolean型变量
//        boolean remove = set.remove(634);
//        System.out.println(remove);
        //在此集合中的元素上创建late-binding和故障快速Spliterator
//        Spliterator spliterator = set.spliterator();
//        System.out.println(spliterator);//输出结果:java.util.HashMap$KeySpliterator@1b6d3586
        //返回HashSet的浅层副本,元素本身不被克隆。
        //浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
        //深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
        System.out.println(set.clone());//想要克隆,需要是子类的实例化,如果是父类指向子类,不能实现该功能
        //foreach遍历该集合
        for (Object o:set) {
            System.out.println(o);
        }
        //迭代器遍历该集合
//        Iterator iterator = set.iterator();
//        while (iterator.hasNext()){
//            System.out.println(iterator.next());
//        }
    }
}

HashSet类中没有提供根据集合索引获取索引对应的值的⽅法,

因此遍历HashSet时需要使⽤Iterator迭代器。Iterator的主要⽅法如下

返回类型方法描述
booleanhasNext()如果有元素可迭代
Objectnext()返回迭代的下⼀个元素

hashCode:散列码是由对象导出的一个整型值。散列码是没有规律的。类的hashCode()方法继承自Object类,因此每个对象都有一个默认的散列码,他的值为对象的存储地址(由对象的物理存储地址通过散列转换来的)。

4.1.2 HashSet的特点

HashSet的底层就是HashMap

HashSet内部的数据结构是哈希表,是线程不安全的。

HashSet中元素不可以重复,是无序的(这里无序是指存入元素的先后顺序与输出元素的先后顺序不一致)

HashSet 中保证集合中元素是唯一的方法:通过对象的hashCodeequals方法来完成对象唯一性的判断。

hashcode的值相同对象不一定是同一个但是hashcode的值不相同对象一定不一样

1)如果对象的hashCode值不同,则不用判断equals方法,就直接存到HashSet中。

2)如果对象的hashCode值相同,需要用equals方法进行比较,如果结果为true,则视为相同元素,不存,如果结果为false,视为不同元素,进行存储。

注意:如果对象元素要存储到HashSet中,必须覆盖hashCode方法和equals方法。才能保证从对象中的内容的角度保证唯一。

类:
public class Student {
    private  String name;
    private Integer age;
    /*
    hashcode的值相同对象不一定是同一个但是hashcode的值不相同对象一定不一样
1)如果对象的hashCode值不同,则不用判断equals方法,就直接存到HashSet中。
2)如果对象的hashCode值相同,需要用equals方法进行比较,如果结果为true,则视为相同元素,不存,如果结果为false,视为不同元素,进行存储。
**注意**:如果对象元素要存储到HashSet中,必须覆盖hashCode方法和equals方法。才能保证从对象中的内容的角度保证唯一。 
    */
​
    @Override
    public boolean equals(Object o) {
        Student student = new Student();
        if (o instanceof Student){
            student = (Student) o;
        }
        if(student.getAge()==this.age&&student.getName().equals(this.name)){
            return true;
        }
        return false;
    }
​
    @Override
    public int hashCode() {
        return name.hashCode()+age.hashCode();//重写equals方法,如果name+age的hashcode值相等初步判定是同一个元素,再去调用equals方法比较内容。
    }
​
   /* @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return Objects.equals(name, student.name) && Objects.equals(age, student.age);
    }
​
    @Override
    public int hashCode() {
         //此处调用的是父类的hashcode方法,返回的是new出来内存的地址值。
        return Objects.hash(name, age);//返回name,age的hashcode值
    }
*/
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    public Integer getAge() {
        return age;
    }
​
    public void setAge(Integer age) {
        this.age = age;
    }
​
    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
​
    public Student() {
    }
​
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
测试:
 public static void main(String[] args) {
        Set<Student> set = new HashSet<>();
        set.add(new Student("lwl",18));
        set.add(new Student("lwl",18));
        System.out.println(set.size());
        for (Student student : set) {
            System.out.println(student);
        }
    }
   public static void main(String[] args) {
     String str1 = "OK";
     StringBuffer str2 = new StringBuffer(str1);
     String str3 = new String(str1);
     StringBuilder str4 = new StringBuilder(str1);
     System.out.println(str1.hashCode());
     System.out.println(str2.hashCode());
     System.out.println(str3.hashCode());
     System.out.println(str4.hashCode());
   }
​
其中str1和str3拥有相同的散列码,这是因为字符串的散列码是由内容导出的。
而字符串穿缓冲str2和str4的却不相等,这是因为他俩没有hashCode方法,他们的散列码由Object的hashCode方法导出的对象的存储地址。

4.2TreeSet

TreeSet实际上是TreeMap实现的。当我们构造TreeSet时;若使用不带参数的构造函数,则TreeSet的使用自然比较器;若用户需要使用自定义的比较器,则需要使用带比较器的参数。需要实现比较器 ,主要是用来比较。

TreeSet是有序的数据集合, 可以对Set集合中的元素进行排序,是线程不安全的。

TreeSet对元素进行排序的方式:

如果是基本数据类型和String类型,无需其它操作,可以直接进行排序。

对象类型元素排序,需要实现Comparable接口,并覆盖其compareTo方法。并按照方法中的定义的规则排序

public class Student implements Comparable{
    //对象需要实现Comparable接口
    int age;
    String name;
​
    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }
    public Student() {
    }
    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
    @Override
    public int compareTo(Object o) {
        Student student = new Student();
        if (o instanceof Student){
            student = (Student) o;
        }
        if (this.age>student.getAge()){
            return 1;
        }else if (this.age<student.getAge()){
            return -1;
        }else {
            return 0;
        }
    }
    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;
    }
}
 public static void main(String[] args) {
        TreeSet set = new TreeSet();
        //因为重写了CompareTo方法,只要年龄不同返回的值就不同,就不是同一个数据
        set.add(new Student(12,"l"));
        set.add(new Student(22,"l"));
        set.add(new Student(33,"l"));
        set.add(new Student(44,"l"));
        System.out.println(set);
​
        //没有重写CopareTo方法
//        TreeSet tree = new TreeSet();
//        tree.add(12);
//        tree.add("12");
//        tree.add("aaa");
//        System.out.println(tree.size());
    }

五、Map

5.1HashMap

存取方式是 key-value形式

Key和value都可以是任意对象类型。

Key不能重复,value可以重复,可以为null。

HashMap是无序的。

如果put重复的key,后⾯的覆盖前⾯的。

5.2 HashMap创建和使用

containsKey("key")获取map中是否包含某个key。

containsValue("value")判断map中是否包含某个value。

public static void main(String[] args) {
        //操作不方便但是扩展方便
        HashMap map = new HashMap();
        map.put("name","ll");
        map.put("age",23);
        map.put("phone","123453125");
        map.put("null","aa");
        map.put("zz",null);//HashMap接收空键空值
        System.out.println(map.get("phone"));//根据key取值,123453125
        System.out.println(map.get("zz"));// null
        System.out.println("*******************");
        HashMap map1 = new HashMap();
        map1.put("name","ls");
        map1.put("address","xxx");
        map1.put("age",22);
        // 遍历所有的值
        Collection coll = map1.values();
        for (Object o : coll) {
            System.out.println(o+"\t");
            /*
            xxx
            ls
            22
             */
        }
        System.out.println("***************");
        //遍历key--可以根据key取值
        Set set = map1.keySet();
        for (Object o : set) {
            System.out.println("key:"+o+"\t"+map1.get(o)+"\t");
            /*
            key:address xxx
            key:name    ls
            key:age 22
             */
        }
        System.out.println("***************");
​
        //遍历所有节点:HashMap的底层有链表
        Set<Map.Entry> set1 = map1.entrySet();
        for (Map.Entry entry : set1) {
            System.out.println(entry.getKey()+":"+entry.getValue()+"\t");
            /*
            address:xxx
            name:ls
            age:22
             */
        }
    }

HashMap的初始容量是16,默认加载因子是0.75,扩容按照原始容量的2倍扩容,可以存储null值和null键,因为没有加锁,当多个线程同时操作一个hashmap就可能出现不安全的情况,所以线程也是不安全的,底层是由数组+链表+红黑树实现。

当添加一个元素(key-value)时,就首先计算元素key的hash值,以此确定插入数组中的位置,但是可能存在同一hash值的元素,这时就添加到同一hash值的元素的后面,他们在数组的同一位置,但是形成了链表,同一各链表上的Hash值是相同的,所以说数组存放的是链表。而当链表长度大于8时,链表就转换为红黑树,以及当数组的长度大于64时,也会自动转换成红黑树,这样大大提高了查找的效率。

通过哈希表函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node(节点)添加到这个位置上。如果说下标对应的位置上有链表。此时,就会拿着key和链表上每个节点的key进行equals。如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals的结果返回了true,那么这个节点的value将会被覆盖

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值