Java语法进阶(1)——容器

1、概念

容器用于装其他对象的对象。

数组是一种简单的容器,优点:效率高、速度快,缺点:使用不够灵活,不能扩容,因此需要功能更加强大的容器,如下容器(集合)接口。

2、Collection集合的常用操作

基础方法:

remove()方法,仅移除容器中记录的对象的地址,并不是删除对象本身,新手容易犯错。

clear()方法,移除容器的所有内容。

toArray()方法,转化为一个object数组。

contains()方法,检查是否包含某个对象。

两个集合的方法:

removeAll()方法,移除A、C的交集元素。

retainAll()方法,保存A、C的交集元素。

3、List接口

List是有序可重复的容器。

有序:List中每个元素都有索引标记。可以根据元素的索引标记(在List中的位置)访问元素,从而精确控制这些元素。

可重复:List允许加入重复的元素。更确切地讲,List通常允许满足e1.equals(e2)的元素重复加入容器。(Set接口则是无序不可重复的方法)

List接口常用实现类有3个:ArrayList(主要使用)、LinkedList、Vector

4、ArrayList接口

ArrayList底层是用数组实现的存储。特点:查询效率高,增删效率低(LinkList)线程不安全(Vector)。我们一般使用它。

底层实现:

数组长度是有限的,而ArrayList是可以存放任意数量的对象,长度不受限制,那么它是怎么实现的呢?使用数组扩容实现,定义更长的数组,将旧内容拷贝到数组前部分,新内容放到旧内容后面,如下:

add()方法中,若就数组不够长度,那么方法调用链中用于数组扩容部分的代码如下:

private void grow(int minCapacity) {

    // overflow-conscious code
    int oldCapacity = elementData.length;//保存旧长度
    int newCapacity = oldCapacity + (oldCapacity >> 1);//新长度=旧长度+旧长度右移一位(即除以2)
    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);//将旧数组拷贝一次,放入以新长度new出来的数组中,将新数组引用返回给原引用对象
}

remove()方法代码如下:

public E remove(int index) {

    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);//从旧数组被移除位置后一个开始,将后面的数据全部copy到旧数组从被移除位置开始,那么被移除位置被覆盖
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

5、手写ArrayList

以下包含手写的ArrayList、泛型、数组扩容、边界检查、移除元素、重写toString()

package net.com.hyh._20201128ArrayLisy_manual;


/***
 * hyh
 * 01自定义实现一个ArrayList
 * 02增加泛型
 * 03增加数组扩容
 * 04增加数组边界检
 * 05增加删除
 * */

public class SxtArrayList05<E> {
    private Object [] elementData;//核心数组存储数据
    private int size;
    private static final int DEFALT_CAPACITY=10;//定义默认长度

    public SxtArrayList05(){
        elementData=new Object[DEFALT_CAPACITY];//无参构造,默认长度
    }
    public SxtArrayList05(int capacity){
        //检查索引
        if(capacity<0)//不合法,手动抛出异常
            throw  new RuntimeException();
        elementData=new Object[capacity];//有参构造,给定长度
    }

    //add方法,添加元素
    //数组扩容
    public void add(E element){

        //什么时候扩容
        if(elementData.length==size)
        {
            //怎么扩容
            //新数组长度=旧数组长度+旧数组长度右移1位,即减半,注:“+”优先级高于“>>”,因此elementData.length>>1要加括号
            Object[] newArray=new Object[elementData.length+(elementData.length>>1)];
            //数组拷贝,将原数组从0开始,拷贝到新数组从0开始,拷贝原数组所有内容
            System.arraycopy(elementData,0,newArray,0,elementData.length);
            //新数组赋给原引用对象,旧数组空间被垃圾回收
            elementData=newArray;
        }
        elementData[size++]=element;
    }

    //重写toString方法
    @Override
    public String toString() {
        StringBuilder sb=new StringBuilder();//临时存储数组
        sb.append("[");
        for(int i=0;i<size;i++){
            sb.append(elementData[i]+",");
        }
        sb.setCharAt(sb.length()-1,']');//替换最后的“,”

        return sb.toString();
    }

    //索引检查方法
    private void checkRange(int index){
        //检查索引
        if(index<0||index>=size)//不合法,手动抛出异常
            throw  new RuntimeException();
    }

    //获得元素
    public E get(int index){
        checkRange(index);
        return (E)elementData[index];
    }


    //设置元素
    public void set(E element,int index){
        checkRange(index);
        elementData[index]=element;
    }

    //按对象移除
    public void remove(E element){
        for(int i=0;i<size;i++)
            if(elementData[i].equals(element))
            {
                remove(i);//全部移除
            }
    }

    //按下标移除
    public void remove(int index){
        checkRange(index);//检查是否越界
        int moveLength=elementData.length-index-1;//获得需要移动部分的长度
        if(moveLength>0) {//若需要移除最后一个元素,则不copy,直接将容器最后一位赋值为null并减小容器长度
            System.arraycopy(elementData, index + 1, elementData, index, moveLength);//对自身copy
        }
        elementData[--size]=null;
    }

    //测试
    public static void main(String[] args) {
        SxtArrayList05 s1=new SxtArrayList05(20);

        for(int i=0;i<40;i++){
            s1.add("elm"+i);
        }
        s1.set("1",1);

        System.out.println(s1);//默认调用toSting方法

        //
        //System.out.println(s1.get(1));
        //System.out.println(s1.get(40));

        s1.remove(39);
        System.out.println(s1);//默认调用toSting方法
        s1.remove(1);
        System.out.println(s1);//默认调用toSting方法
        s1.remove("elm4");
        System.out.println(s1);//默认调用toSting方法

    }

}

6、手写LinkedList(查询效率低,不常用)

LinkedList底层用双向链表实现的存储。特点:查询效率低,增删效率高,线程不安全。

双向链表也叫双链表,是链表的一种,它的每个数据节点中都有两个指针,分别指向前一个节点和后一个节点。 所以,从双向链表中的任意一个节点开始,都可以很方便地找到所有节点。结构如下图(下图有误,不带头结点,且不循环):

定义:

public class Node {

    Node pre;
    Node next;
    Object element;
}

以下包括LinkedList的get方法、检查方法、Remove方法、插入结点方法、泛型等。

package net.com.hyh._20201201ArrayList_manual;

/**
 * 定义一个链表
 *添加get、检查方法
 *增加Remove
 *添加插入结点
 *增加泛型
 *
 */

public class MyLinkList04<E> {
    private Node first;//头指针
    private Node last;//尾指针
    private int size;

    public MyLinkList04() {//构造函数,带头结点的链表

    }

    private void checkRange(int index){
        if(index<0||index>=size){
            throw new RuntimeException("索引数字不合法:"+index);
        }
    }

    //添加元素
    //["a","b","c"]
    public void add(E element){
        Node node=new Node(element);
        if(first==null){
            first=node;
            last=node;
        }
        else {
            node.pre = last;
            node.next = null;

            last.next = node;
            last = node;
        }
        size++;
    }

    @Override
    public String toString() {
        Node temp=first;
        StringBuilder sb=new StringBuilder();
        sb.append("[");
        while (temp!=null){
            sb.append(temp.element+",");
            temp=temp.next;//注意:指针不能忘了后移
        }
        sb.setCharAt(sb.length()-1,']');
        return sb.toString();

//        Node temp=first;
//        while (temp!=null){
//            System.out.println(temp.element);
//            temp=temp.next;
//        }
//        return "";

    }

    //["a","b","c","d","e","f","g"]
    public E get(int index){
        Node temp=getNode(index);
        return temp!=null?(E)temp.element:null;
    }

    private Node getNode(int index)
    {
        checkRange(index);
        Node temp=null;
        if(index<(size>>1)){//size>>1相当于除以2
            temp =first;
            for(int i=0;i<index;i++)
            {
                temp=temp.next;
            }
        }
        else{//若查找位置在链表后半段,则从后向前找
            temp =last;
            for(int i=size-1;i>index;i--)
                temp=temp.pre;
        }
        return temp;
    }

    public void remove(int index)
    {
        Node temp=getNode(index);
        if(temp!=null) {
            if (temp == first) {
                first = first.next;
                first.pre = null;
            } else if (temp == last) {
                last = last.pre;
                last.next = null;
            } else {
                temp.pre.next = temp.next;
                temp.next.pre = temp.pre;
            }
        }
        size--;
    }

    public void insert(Object obj,int index)
    {
        Node temp=getNode(index);
        if(temp!=null) {
            Node newNode=new Node(obj);
            if (temp == first)
            {
                newNode.next=first;
                first.pre=newNode;
                first=newNode;
            }
            else{
                newNode.next=temp;
                newNode.pre=temp.pre;

                newNode.pre.next=newNode;
                temp.pre=newNode;
            }
        }
    }

    //测试
    public static void main(String[] args) {
        MyLinkList04<String> l1=new MyLinkList04<>();
        l1.add("1");
        l1.add("2");
        l1.add("3");
        l1.add("4");
        l1.add("5");
        l1.add("6");
        l1.remove(5);
        l1.insert("5",0);
        System.out.println(l1);
    }
}

7、Vector向量(线程安全,效率低,不常用)

Vector底层是用数组实现的List,相关的方法都加了同步检查,因此“线程安全,效率低”。 比如,indexOf方法就增加了synchronized同步标记(多线程方法)。

如何选用ArrayList、LinkedList、Vector?

      1)需要线程安全时,用Vector。

      2)不存在线程安全问题时,并且查找较多用ArrayList(一般使用它)。

      3)不存在线程安全问题时,增加或删除元素较多用LinkedList。

8、Map接口

(1)概念

Map就是用来存储“键(key)-值(value) 对”的。 Map类中存储的“键值对”通过键来标识,所以“键对象”不能重复。(调用equals方法比较,重复则覆盖)

常用方法:

HashMap底层实现采用了哈希表,这是一种非常重要的数据结构。

数据结构中由数组和链表来实现对数据的存储,他们各有特点。

(1) 数组:占用空间连续。 寻址容易,查询速度快。但是,增加和删除效率非常低。

(2) 链表:占用空间不连续。 寻址困难,查询速度慢。但是,增加和删除效率非常高。

那么,我们能不能结合数组和链表的优点(即查询快,增删效率也高)呢? 答案就是“哈希表”。 哈希表的本质就是“数组+链表”。

存储示意图:

JDK8中,当链表长度大于8时,链表就转换为红黑树,这样又大大提高了查找的效率。

(2)手动实现HashMap

以下包括put方法、get方法、toString方法、泛型

package net.com.hyh._20201203;


import java.util.Arrays;

/**
 * 自定义HashMap
 * put方法
 * toString方法
 *泛型
 */

public class MyHashArrayNode<K,V> {
    MyHashListNode[] table;
    int size;

    public MyHashArrayNode() {
        this.table = new MyHashListNode[16];
    }

    public void put(K key,V value){
        //若要再完整一点,考虑数组扩容
        MyHashListNode newNode=new MyHashListNode();
        newNode.hash=myHash(key.hashCode(),table.length);
        newNode.key=key;
        newNode.value=value;
        newNode.next=null;

        MyHashListNode temp=table[newNode.hash];
        if(temp==null) {//若此处数组元素为空,则直接将新结点放进去
            table[newNode.hash]=newNode;
            size++;
        }
        else {//若此处结点不为空,则遍历链表
            MyHashListNode lastNode=null;
            while (temp!=null){
                if(temp.key.equals(newNode.key))//key重复,覆盖
                {
                    temp.value=newNode.value;
                    break;//找到重复值,结束循环
                }
                else {//不重复,后移
                    lastNode=temp;
                    temp = temp.next;
                }
            }
            if(temp==null) {//链表遍历完毕没有重复值
                lastNode.next = newNode;
                size++;
            }
        }
    }

    public int myHash(int v,int length){
        //System.out.println("&:"+(v&(length-1)));//直接位运算,效率高     10100
        //System.out.println("%:"+(v%(length-1)));//取模运算,效率低       01111
        return v&(length-1);                      //                     00100 = 4
    }

    @Override
    public String toString() {
        StringBuilder sb=new StringBuilder("{");//存储遍历到的数据

        for (int i=0;i<table.length;i++)//先遍历数组
        {
            MyHashListNode temp=table[i];
            while (temp!=null){//遍历链表
                sb.append(temp.key+":"+temp.value+",");
                temp=temp.next;
            }
        }
        sb.setCharAt(sb.length()-1,'}');

        return sb.toString();
    }

    public V get(K key){//根据key对象获得对应的value值
        int hash=myHash(key.hashCode(),table.length);//获得散列值
        MyHashListNode temp=table[hash];
        while (temp!=null){
            if(temp.key.equals(key)) {//相等则知道了key对象
                return (V) temp.value;
            }
            else
                temp=temp.next;
        }
        return null;
    }

    public static void main(String[] args) {
        MyHashArrayNode<Integer,String > m=new MyHashArrayNode<>();
        m.put(10,"a");
        m.put(20,"b");
        m.put(20,"b0");

        m.put(53,"b1");
        m.put(69,"b2");
        m.put(85,"b3");

        System.out.println(m);
//        for(int i=0;i<100;i++)
//            System.out.println(i+"->"+m.myHash(i,16));
        System.out.println(m.get(53));

    }
}

(3)TreeMap

TreeMap是红黑二叉树的典型实现。我们打开TreeMap的源码,发现里面有一行核心代码:

 

private transient Entry<K,V> root = null;

root用来存储整个树的根节点。我们继续跟踪Entry(是TreeMap的内部类)的代码:

图9-23 Entry底层源码

可以看到里面存储了本身数据、左节点、右节点、父节点、以及节点颜色。 TreeMap的put()/remove()方法大量使用了红黑树的理论。本书限于篇幅,不再展开。需要了解更深入的,可以参考专门的数据结构书籍。

TreeMap和HashMap实现了同样的接口Map,因此,用法对于调用者来说没有区别。HashMap效率高于TreeMap;在需要排序的Map时才选用TreeMap。

9、Set接口

Set接口继承自Collection,Set接口中没有新增方法,方法和Collection保持完全一致。我们在前面通过List学习的方法,在Set中仍然适用。因此,学习Set的使用将没有任何难度。

Set容器特点:无序、不可重复。无序指Set中的元素没有索引,我们只能遍历查找;不可重复指不允许加入重复的元素。更确切地讲,新元素如果和Set中某个元素通过equals()方法对比为true,则不能加入;甚至,Set中也只能放入一个null元素,不能多个。

Set常用的实现类有:HashSet、TreeSet等,我们一般使用HashSet。

list容器特点:有顺序,可重复

(1)HashSet

HashSet是采用哈希算法实现,底层实际是用HashMap实现的(HashSet本质就是一个简化版的HashMap),因此,查询效率和增删效率都比较高。

我们发现里面有个map属性,这就是HashSet的核心秘密。我们再看add()方法,发现增加一个元素说白了就是在map中增加一个键值对,键对象就是这个元素,值对象是名为PRESENT的Object对象。说白了,就是“往set中加入元素,本质就是把这个元素作为key加入到了内部的map中”。

(2)TressSet

TreeSet底层实际是用TreeMap实现的,内部维持了一个简化版的TreeMap,通过key来存储Set的元素。 TreeSet内部需要对存储的元素进行排序,因此,我们对应的类需要实现Comparable接口。这样,才能根据compareTo()方法比较对象之间的大小,才能进行内部排序。

使用TreeSet要点:

 (1) 由于是二叉树,需要对元素做内部排序。 如果要放入TreeSet中的类没有实现Comparable接口,则会抛出异常:java.lang.ClassCastException。

 (2) TreeSet中不能放入null元素。

10、迭代器

迭代器为提供了统一的遍历容器的方式。

以下包含List、Set、Map的遍历方式

package net.com.hyh._20201207;

import java.util.*;

public class TestIterator {
    public static void main(String[] args) {
        //testIeratorList();
        //testIeratorSet();
        testIeratorMap();
    }

    public  static void testIeratorList()
    {
        List<String> list=new ArrayList<>();
        list.add("aa");
        list.add("bb");
        list.add("cc");

        //使用iterator遍历list
        for(Iterator<String>iter=list.iterator();iter.hasNext();)
        {
            //System.out.println(iter.hasNext());
            String temp=iter.next();//返回对象的同时将指针后移
            System.out.println(temp);
        }
    }

    public  static void testIeratorSet()
    {
        Set<String> set=new HashSet<>();
        set.add("aa");
        set.add("bb");
        set.add("cc");

        //使用iterator遍历set
        for(Iterator<String>iter=set.iterator();iter.hasNext();)
        {
            //System.out.println(iter.hasNext());
            String temp=iter.next();//返回对象的同时将指针后移
            System.out.println(temp);
        }
    }

    public  static void testIeratorMap()
    {
        Map<Integer,String> map=new HashMap<>();
        map.put(100,"aa");
        map.put(13,"bb");
        map.put(12,"cc");

        //第一种遍历Map方式
        //使用iterator遍历map,先将键值对存入set,再遍历set
        Set<Map.Entry<Integer,String>> ss=map.entrySet();
        for(Iterator<Map.Entry<Integer,String>> iter = ss.iterator(); iter.hasNext();)
        {
            //System.out.println(iter.hasNext());
            Map.Entry<Integer,String> temp=iter.next();//返回对象的同时将指针后移
            System.out.println(temp.getKey()+"--"+temp.getValue());
        }

        //第二种遍历Map方式
        Set<Integer> keySet=map.keySet();
        for(Iterator<Integer> iter=keySet.iterator();iter.hasNext();)
        {
            //System.out.println(iter.hasNext());
            Integer key=iter.next();//返回对象的同时将指针后移
            System.out.println(key+"--"+map.get(key));
        }
    }
}

 

11、容器遍历方法总结

【示例9-15】遍历List方法一:普通for循环

1

2

3

4

for(int i=0;i<list.size();i++){//list为集合的对象名

    String temp = (String)list.get(i);

    System.out.println(temp);

}

【示例9-16】遍历List方法二:增强for循环(使用泛型!)

1

2

3

for (String temp : list) {

System.out.println(temp);

}

【示例9-17】遍历List方法三:使用Iterator迭代器(1)

1

2

3

4

for(Iterator iter= list.iterator();iter.hasNext();){

    String temp = (String)iter.next();

    System.out.println(temp);

}

【示例9-18】遍历List方法四:使用Iterator迭代器(2)

1

2

3

4

5

6

Iterator  iter =list.iterator();

while(iter.hasNext()){

    Object  obj =  iter.next();

    iter.remove();//如果要遍历时,删除集合中的元素,建议使用这种方式!

    System.out.println(obj);

}

【示例9-19】遍历Set方法一:增强for循环

1

2

3

for(String temp:set){

System.out.println(temp);

}

【示例9-20】遍历Set方法二:使用Iterator迭代器

1

2

3

4

for(Iterator iter = set.iterator();iter.hasNext();){

    String temp = (String)iter.next();

    System.out.println(temp);

}

【示例9-21】遍历Map方法一:根据key获取value

1

2

3

4

5

Map<Integer, Man> maps = new HashMap<Integer, Man>();

Set<Integer>  keySet =  maps.keySet();

for(Integer id : keySet){

System.out.println(maps.get(id).name);

}

【示例9-22】遍历Map方法二:使用entrySet

1

2

3

4

Set<Entry<Integer, Man>>  ss = maps.entrySet();

for (Iterator iterator = ss.iterator(); iterator.hasNext();) {

    Entry e = (Entry) iterator.next(); 

    System.out.println(e.getKey()+"--"+e.getValue());

 

12、Collection工具类

 

  类 java.util.Collections 提供了对Set、List、Map进行排序、填充、查找元素的辅助方法。

      1. void sort(List) //对List容器内的元素排序,排序的规则是按照升序进行排序。

      2. void shuffle(List) //对List容器内的元素进行随机排列。

      3. void reverse(List) //对List容器内的元素进行逆续排列 。

      4. void fill(List, Object) //用一个特定的对象重写整个List容器。

      5. int binarySearch(List, Object)//对于顺序的List容器,采用折半查找的方法查找特定对象。

public class Test {

    public static void main(String[] args) {
        List<String> aList = new ArrayList<String>();
        for (int i = 0; i < 5; i++){
            aList.add("a" + i);
        }
        System.out.println(aList);
        Collections.shuffle(aList); // 随机排列
        System.out.println(aList);
        Collections.reverse(aList); // 逆序
        System.out.println(aList);
        Collections.sort(aList); // 排序
        System.out.println(aList);
        System.out.println(Collections.binarySearch(aList, "a2"));
        Collections.fill(aList, "hello");
        System.out.println(aList);
    }
}

【示例9-23】Collections工具类的常用方法

 

执行结果如图9-31所示:

 

13、用Map和List存储表格

每一行数据使用一个Map。

整个表格使用一个List。

ORM思想:对象关系映射(将表格映射成对象)

package net.com.hyh._20201207;


import java.util.*;

public class TestStoreData {
    public static void main(String[] args) {
        Map<String,Object> row1=new HashMap<>();
        row1.put("ID",1001);
        row1.put("姓名","张三");
        row1.put("Salary",20000);
        row1.put("入职日期",20201102);

        Map<String,Object> row2=new HashMap<>();
        row2.put("ID",1002);
        row2.put("姓名","李四");
        row2.put("Salary",10000);
        row2.put("入职日期",20201101);

        Map<String,Object> row3=new HashMap<>();
        row3.put("ID",1003);
        row3.put("姓名","王五");
        row3.put("Salary",30000);
        row3.put("入职日期",20201103);

        List<Map<String,Object>> table=new ArrayList<>();
        table.add(row1);
        table.add(row2);
        table.add(row3);

//        for(Map<String,Object> row:table)
//        {
//            System.out.println(row);
//        }

        for(Map<String,Object> row:table)
        {
            Set<String> keyset=row.keySet();//获得所有的键

            for(String key:keyset)//挨个打印每个键的值
                System.out.print(key+":"+row.get(key)+"\t");
            System.out.println();
        }
    }
}

13、用JavaBe和List存储表格

每一行数据使用一个javabean对象。

整个表格使用一个List/Map。

ORM思想:对象关系映射(将表格映射成对象)

package net.com.hyh._20201207;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TestStoreData2 {
    public static void main(String[] args) {
        User user1=new User(1001,"张三",10000,"20201101");
        User user2=new User(1002,"李四",20000,"20201102");
        User user3=new User(1003,"王五",30000,"20201103");

        //map存储表格
        Map<Integer,User> table1=new HashMap<>();
        table1.put(1,user1);
        table1.put(2,user2);
        table1.put(3,user3);

        //list存储表格
        List<User> table2=new ArrayList<>();
        table2.add(user1);
        table2.add(user2);
        table2.add(user3);

        //直接打印Map中的数据,keySet可以全部存入到Set对象中再遍历取值
        System.out.println("table1:");
        for(Integer key:table1.keySet())
            System.out.println(
                    "id="+table1.get(key).getId()+
                    ",name=\'"+table1.get(key).getName()+"\'"+
                    ",salary="+table1.get(key).getSalary()+
                    ",hiredate=\'"+table1.get(key).getHiredate()+"\'");


        //重写toString打印List中的数据
        System.out.println("table2:");
        for (User u:table2)
            System.out.println(u.toString());

    }
}

class User{
    private int id;
    private String name;
    private double salary;
    private String hiredate;

    @Override
    public String toString() {
        return  "id=" + id +
                ", name='" + name + '\'' +
                ", salary=" + salary +
                ", hiredate='" + hiredate + '\'';
    }

    //一个完整的Javabean要有set和get方法,以及无参构造器
    public User() {
    }

    public User(int id, String name, double salary, String hiredate) {
        this.id = id;
        this.name = name;
        this.salary = salary;
        this.hiredate = hiredate;
    }

    public void setId(int id) {
        this.id = id;
    }

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

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public void setHiredate(String hiredate) {
        this.hiredate = hiredate;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public double getSalary() {
        return salary;
    }

    public String getHiredate() {
        return hiredate;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值