List 接口

List集合是线性数据结构的主要表现,集合元素通常存在着明确的上一个和下一个元素,也存在着明确的第一个元素和最后一个元素。最常用的类有ArrayList,LinkedList

AbstractList 抽象类

AbstractList<E>抽象类实现了List<E>接口,实现了一些基本的方法,并且在add,remove,set方法中直接抛出了UnsupportedOperationException异常,这就意味着如果子类直接继承这个AbstractList然后不重写就直接使用这写方法就会抛出UnsupportedOperationException异常。

ArrayList

需实现set方法 ---- AbstractLIst

根据索引访问集合元素 ----List

可以随机访问 ----RandomAccess

能克隆---cloneable标记接口,重写Object 中的clone 方法(否则抛出CloneNotSupportedException 克隆不被支持)

可以被序列化或者反序列化----Serializable

非同步---若想线程安全:Collections.synchronizedList(new LinkedList());

 

ArrayList 底层是数组(实现接口RandomAccess 随机访问标识接口),擅长随机方法,初始容量为10,每次扩容为1.5倍,线程不安全,若想线程安全:Collections.synchronizedList(new ArrayList ());

ArrayList构造方法

/**
 *默认构造函数,使用初始容量10构造一个空列表(无参数构造)
 */
public ArrayListDemo(){
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}


/**
 * 带初始容量参数的构造函数。(用户自己指定容量)
 */
public ArrayListDemo(int initCapacity){


    if(initCapacity>0){
        this.elementData = new Object[initCapacity];
    }
    else if(initCapacity ==0){
        this.elementData = EMPTY_ELEMENTDATA;
    }
    else {
         throw new IllegalArgumentException("illegal initCapacity:" + initCapacity);
    }


}


/**
 *构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回
 *如果指定的集合为null,throws NullPointerException。
 */


public ArrayListDemo(Collection<? extends E> c){
    elementData = c.toArray();


    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }


    if( 0 != (size = elementData.length)){
        if (Object[].class != elementData.getClass()){
            elementData = Arrays.copyOf(elementData,size, Object[].class);
        }
    }else{
        this.elementData = EMPTY_ELEMENTDATA;
   

ArrayList扩容机制

自己模仿实现的扩容机制:

public boolean add(Object o) {
    ensureCapacityInternalDemo(size + 1);  // Increments modCount!!
    //这里看到ArrayList添加元素的实质就相当于为数组赋值
    elementData[size] = o;
    size = size +1;
    return true;


}


public void  ensureCapacityInternalDemo(int minCapacity){


    if(DEFAULTCAPACITY_EMPTY_ELEMENTDATA ==  this.elementData){
        minCapacity = Math.max(minCapacity, DEFALUT_CAPACITY);
    }


    ensureExplicitCapacityDemo(minCapacity);
}




public void ensureExplicitCapacityDemo(int minCapacity){


    if(minCapacity - this.elementData.length > 0){
        growDemo(minCapacity);
    }


}


public void growDemo(int minCapacity){
    //旧的容量
    int oldCapacity = this.elementData.length;
    //计算新的容量:将oldCapacity右移一位,也就是oldCapacity的一半
    int newCapacity = oldCapacity + (oldCapacity >>1);
    //判断新的容量是否满足需求:是否小于当前要求的最小容量,若是有小于则新的容量为最小容量
    if(minCapacity - newCapacity>0){
        newCapacity = minCapacity;
    }
    //判断新的容量是否大于最大值:MAX_ARRAY_SIZE,若是大于为Integer.MAX_VALUE
    if(newCapacity - MAX_ARRAY_SIZE>0){
        newCapacity = Integer.MAX_VALUE;
    }
    //将源素组复制到新数组
    elementData = Arrays.copyOf(elementData,newCapac

若是新建没有给定初始容量,如List list = new ArrayListDemo(),此时的list 的为空,只有在第一次add 操作的时候,会进行首次扩容,大小为10,若下图所示,newCapacity/minCapacity 即为list 内部的数组elementData.length,因此为了性能考虑,在新建的时候最好可以给定list 的初始容量,减少反复扩容的性能消耗

 

若是新建给定初始容量,如List list = new ArrayListDemo(10),那么在前10次add操作的时候都不会扩容,因为当add前10个元素的时, elemenetData.length为10,因为执行了ensureExplicitCapacityDemo方法, minCapacity - this.elementData.length > 0 不成立,所以不会进入growDemo 方法,不会进行扩容,但是当第十一次add 操作时,minCapacity 为11, minCapacity - this.elementData.length > 0 成立,会进入 growDemo 方法,得到新的容量为为旧容量的1.5倍,即为15,最后list.size 为11,运行结果看下图

 

 

 

LinkedList

 LinkedList底层是链表,同时实现List<E>, Deque<E>, Cloneable, Serializable

是个接口,继承了AbstractSequentialList抽象类,因此LinkedList出游如下特性:

 

需实现set方法 ---- AbstractLIst

根据索引访问集合元素 ----List

双向队列,既可以是栈,也可以是队列 ----Deque

能克隆---cloneable标记接口,重写Object 中的clone 方法(否则抛出CloneNotSupportedException 克隆不被支持)

可以被序列化或者反序列化----Serializable

非同步---若想线程安全:Collections.synchronizedList(new LinkedList());

 

所以LinkedList 不支持随机访问,但是插入删除性能好,没有初始大小,也没有扩容机制,作为双向链表,只有在头部或者尾部新增即可,

 

 

内部分析结构图

 

 

 

内部类

//内部类
private static class Node<E>{
    E data; //节点值
    Node pre;// 前驱节点
    Node next;//后继节点
    Node( Node pre,E data, Node next){
        this.data= data;
        this.pre =pre;
        this.next = next;
    }
}

 

LinkedList 构造方法

//空构造方法
public LinkedListDemo(){};


//已有集合的构造方法
public  LinkedListDemo(Collection <? extends E> collection){
    this();
    addAll(collection);


}

模拟LinkedList 源码实现的重要方法

package com.list;


import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;


public class LinkedListDemo<E> {


    //内部类
    private static class Node<E>{
        E data; //节点值
        Node pre;// 前驱节点
        Node next;//后继节点
        Node( Node pre,E data, Node next){
            this.data= data;
            this.pre =pre;
            this.next = next;
        }
    }
    transient  int size;
    transient  Node<E> fisrt;
    transient  Node<E> last;
    //空构造方法
    public LinkedListDemo(){};


    //已有集合的构造方法
    public  LinkedListDemo(Collection <? extends E> collection){
        this();
        addAll(collection);


    }


    public int size() {
        return this.size;
    }


    //链接在尾部
    private   boolean linkLast(E e){
        final Node<E> l = last;
        final Node<E> newNode = new Node<E>(l, e, null);
        last = newNode;
        if(null == l){
            fisrt = last;
        }
        else{
            l.next = newNode;
        }
        size ++;
        return true;
    }


    //增加在头部
    public boolean LinkFirst(E e){
        final Node<E> f = fisrt;
        final Node<E> newNode = new Node<E>(null,e,f);
        fisrt = newNode;
        if(null == f){
            last = fisrt;
        }else{
            f.pre = fisrt;
        }


        size++;
        return  true;
    }


    //默认添加在尾部
    public boolean add(E data){
        this.linkLast(data);
        return  true;
    }


    //加入到指定位置
    public boolean add(E e, int index){
        //判断Index 是否在 0-size 之间
        checkPositionCheck(index);
        if(index == size){
            this.linkLast(e);
        }else{
            //获取index 索引的节点
            Node tempNode = this.Node(index);
            //将数据插入的该节点之前
            this.linkedBefore(e,tempNode);


        }
        return true;
    }


    private  Node Node(int index){
        Node result;
        if(index < size){
            result = this.fisrt;
            for(int i=0;i<index;i++){
                result = result.next;
            }
        }else{
            result = this.last;
        }
        return result;
    }


    //将data 插入到 node 节点之前


    private void linkedBefore(E data, Node<E> node){
        Node tempPre = node.pre;
        Node newNode = new Node(tempPre,data, node);
        node.pre = newNode;
        if(null == tempPre){
            this.fisrt = newNode;
        }else{
            tempPre.next = newNode;
        }
        ++this.size;




    }


    public  boolean addAll(Collection collection){
        addAll(size,collection);
        return true;
    }


    public boolean addAll(int index, Collection<? extends E> collection){
        //1.检查index 是否为有效索引
        checkPositionCheck(index);
        //2.将集合中的元素转存到数组中
        Object[] a = collection.toArray();
        int numNew = a.length;
        if (0 == numNew) return false;


        //3.获取要插入位置的节点
        Node tempNode = Node(index);


        //4.获取插入节点的前驱
        Node tempNodePre = tempNode.pre;


        //数据遍历插入
        for(Object o : a){


            //创新新的节点
            Node newTemp = new Node(tempNodePre,o,null);
            if(null == tempNodePre){
                fisrt = newTemp;
            }else{
                tempNodePre.next = newTemp;
            }


            tempNodePre  = newTemp;
        }


        if(null == tempNode){
            last = tempNodePre;
        }else{
            tempNodePre.next = tempNode;
            tempNode.pre = tempNodePre;
        }
        size = size + numNew;
        return true;
    }


    //根据指定索引返回数据
    public E get(int index){
        //1.检查索引是否有效
        checkPositionCheck(index);
        //2.获取index出的节点
        Node<E> node = Node(index);
        return node.data;
    }


    //获取头节点(index=0)数据方法
    public E getFirst(){
        final Node<E> f =fisrt;
        if (null == f )
            throw  new NoSuchElementException();


        return  f.data;
    }


    public E element(){ return getFirst();}


    public E peek(){
        final Node<E> f = fisrt;
        return (null == f)?null:f.data;
    }


    public E peekFirst() {
        final Node<E> f = fisrt;
        return (f == null) ? null : f.data;
    }


    //获取尾节点(index=-1)数据方法:


    public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.data;
    }
    public E peekLast() {
        final Node<E> l = last;
        return (l == null) ? null : l.data;
    }


    //根据对象得到索引的方法:int indexOf(Object o): 从头遍历找


    public int indexOf(Object o){
        int index =0;
        if(null == o){
            for(Node x = fisrt;x!=null;x = x.next){
                if(null == x.data){
                    return index;
                }
                index ++;
            }
        }else{
            for(Node x = fisrt;x!=null;x = x.next){
                if(o.equals(x.data)){
                    return index;
                }
                index ++;
            }
        }


        return  -1;
    }


    //int lastIndexOf(Object o): 从尾遍历找
    public int lastIndexOf(Object o){
        int index =size -1 ;
        if(null == o){
            for(Node x = last;x!=null;x = x.pre){
                if(null == x.data){
                    return index;
                }
                index --;
            }
        }else{
            for(Node x = last;x!=null;x = x.pre){
                if(o.equals(x.data)){
                    return index;
                }
                index ++;
            }
        }


        return  -1;
    }


    //检查对象o是否存在于链表中
    public boolean contains(Object o){
        return  indexOf(o) !=-1;
    }


    //remove(Object o): 删除指定元素
    public boolean remove(Object o){
        int index =0;
        if(null == o){
            for(Node x = fisrt;x!=null;x = x.next){
                if(null == x.data){
                   unLinked(x);
                   return true;
                }
            }
        }else{
            for(Node x = fisrt;x!=null;x = x.next){
                if(o.equals(x.data)){
                    unLinked(x);
                    return true;
                }
            }
        }


        return false;
    }




    //删除指定位置的元素
    public E remove(int index){
        //检查索引
        checkPositionCheck(index);
        //获取该索引节点
        Node<E> nodeTemp = Node(index);


        //删除节点nodeTemp
        return unLinked(nodeTemp);
    }






    public E pop() {
        return removeFirst();
    }
    public E remove() {
        return removeFirst();
    }
    public E removeFirst() {
        final Node<E> f = fisrt;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }


    public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }
    public E pollLast() {
        final Node<E> l = last;
        return (l == null) ? null : unlinkLast(l);
    }


    public E unlinkFirst(Node<E> node){
        E element = node.data;
        fisrt = node.next;
        node.data = null;
        node.next = null;
        if(null == fisrt){
            last = null;
        }else{
            fisrt.pre = null;
        }
        size--;


        return element;


    }


    public E unlinkLast(Node<E> node){
        E element = node.data;
        last = node.pre;
        node.data = null;
        node.next = null;
        if(null == last){
            fisrt = null;
        }else{
            last.next = null;
        }
        size--;
        return element;


    }


    //删除节点node:节点包含三部分,前驱指针,元素,后继指针,分别删除即可
    private E unLinked(Node<E> node){
        final E element = node.data;
        //前驱
        Node<E> tempPre =  node.pre;
        //后继
        Node<E> tempNext = node.next;


        //前驱指针删除
        if (null == tempNext) {
            fisrt = tempNext;
        }else{
            tempPre.next = tempNext;
            node.pre =null;
        }
        //后继指针删除
        if(null == tempNext){
            last = tempPre;
        }else{
            tempNext.pre = tempPre;
            node.pre = null;
        }
        //删除元素
        node.data = null;
        //修改列表的size
        size --;
        return  element;


    }


    //校验索引是否有效
    private void checkPositionCheck(int index){
        if(!this.isPositionIndex(index)){
            throw new IndexOutOfBoundsException("size is:" +size +"; index is:" + index);
        }


    }


    //检查是index 是否有效索引
    private  boolean isPositionIndex(int index){
        return index>=0 && index<=this.size;
    }


    public void clear() {
        Node<E> node = null;
        for(Node<E> x= fisrt; x!=null; x = x.next){
            node = x.next;
            x.pre =null;
            x.data = null;
            x.next = null;
        }
        this.fisrt = this.last =null;
        this.size =0;


    }


    public Iterator iterator() {


        return new Iterator() {
            // 定义一个变量指向当前迭代的节点,初始值是表头
            Node current = fisrt;


            @Override
            public Object next() {
                // 获得当前节点的内容
                Object o = current.pre;
                // 使指针指向下一个节点
                current = current.next;
                return o;
            }


            @Override
            public boolean hasNext() {
                // 判断当前节点是否为空,部位空的话表面链表还有未迭代的节点,返回true
           

 

 

ArrayLsit VS LinkeList

1、底层实现--ArrayList:数组;LinkedList:链表;

2、随机访问--ArrayList支撑,LinkedList不支持;

3、插入删除--add/remove(index):ArrayList时间复制度为O(n-index),LinkedList接近n(1);

4、内存浪费--ArrayList提现在有一定预留空间,LinkedList提现需要存储前驱后继指针;

5、都是非同步的。

 

Vector

      与ArrayList相似,但是Vector是同步的。所以说Vector是线程安全的动态数组。它的操作与ArrayList几乎一样。

 

源码分析

    protected Object[] elementData;//存放元素的数组 也就是vector底层是数组
    protected int elementCount;//记录存放的元素个数
    protected int capacityIncrement;//增长因子 和增长容量相关 下面会介绍
    private static final long serialVersionUID = -2767605614048989439L;//序列版本号
    public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }
public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }
public Vector() {
        this(10);
    }
public Vector(Collection<? extends E> c) {
        elementData = c.toArray();
        elementCount = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
    }

Vector 的初始容量为10,加载因子默认为0

 

添加扩容机制

add方法:

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

可以看到实现方式为modCount加1,表示修改次数加了一次,这个字段的作用体现在了快速失败机制中,而所谓快速失败机制就是如果在迭代的过程中,这个Vector被修改了,就不冒险继续迭代,而是直接抛出ConcurrentModificationException异常结束。然后调用了一个ensureCapacityHelper(elementCount+1)以确保容量足够,最后将obj加入到存放元素的数组。整个方法是用synchronized关键字修饰的,所以是线程安全的。

 

ensureCapacityHelper():

private void ensureCapacityHelper(int minCapacity) {
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
}

如果所需最小容量比当前存放的数组容量还要大,就调用grow()方法进行扩容,由于执行这个方法的时候已经是在synchronized方法内,所以这个ensureCapacityHelper()方法没有用synchronized关键字修饰。

 

grow()方法:

    private void grow(int minCapacity) {
        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);
    }
}

 

Vector,如果capacityIncrement大于0,newCapacity为capacityIncrement+oldCapacity,否则直接等于oldCapacity的两倍。如果容量仍然不够,newCapacity直接等于所需最小容量。如果newCapacity这个时候比MAX_ARRAY_SIZE还大,也就是比Integer.MAX_VALUE-8还要大就通过hugeCapacity(minCapacity)方法返回一个合适的容量,作为newCapacity,最后使用Arrays.copyOf(elementData,newCapacity)方法来实现数组的扩容。

 

hugeCapacity()方法如下:

 private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
}

 

 

删除机制

removeElement(Object obj)方法:

public synchronized boolean removeElement(Object obj) {
        modCount++;
        int i = indexOf(obj);
        if (i >= 0) {
            removeElementAt(i);
            return true;
        }
        return false;
}

通过indexOf(obj)方法查找obj的索引i,然后调用removeElementAt(i)进行删除。我们直接看removeElementAt(int i)方法:
 public synchronized void removeElementAt(int index) {
        modCount++;
        if (index >= elementCount) {
            throw new ArrayIndexOutOfBoundsException(index + " >= " +
                                                     elementCount);
        }
        else if (index < 0) {
            throw new ArrayIndexOutOfBoundsException(index);
        }
        int j = elementCount - index - 1;
        if (j > 0) {
            System.arraycopy(elementData, index + 1, elementData, index, j);
        }
        elementCount--;
        elementData[elementCount] = null; /* to let gc do its work */
}

删除就没有添加那么麻烦,只是判断一下索引的合法性,然后就使用System.arraycopy()方法进行删除了,注意这里有个小细节:elementData[elementCount] = null,如果没有这行代码会导致elementData[elementCount]这个元素虽然不会再被使用了,但是由于这个元素仍然被elementData这个数组维护着,所以导致这个元素无法被GC回收,当这种情况出现的次数太多,就有可能导致OOM。

 

 

总结

1、底层是用数组实现的,不过是可变长的,默认初始容量是10,默认增长因子是0,如果想要加入新的元素而容量不足就需要进行扩容,如果增长因子大于0,就增长负载因子个数的容量,否则增长为原来容量的两倍,如果容量仍然不够,就直接增长为所需最小容量。频繁地扩容容易引起效率问题,所以最好在调用构造函数的时候指定一个合适的容量或者调用ensureCapacity()方法进行扩容到适当的容量。

2、这个类是线程安全的,采用了快速失败机制,提供了增加、删除元素,更加方便快捷。

3、线程安全不意味着对于这个容器的任何操作都是线程安全的,比如在进行迭代的时候,如果不增加一些代码保证其线程安全,其他线程是可以对这个容器做出修改的,这样也就会导致抛出ConcurrentModificationException异常

 

Stack

     Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop 方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。

 

源码分析

package java.util;


public
class Stack<E> extends Vector<E> {
    // 版本ID。这个用于版本升级控制,这里不须理会!
    private static final long serialVersionUID = 1224463164541339165L;


    // 构造函数
    public Stack() {
    }
  // push函数:将元素存入栈顶
    public E push(E item) {
        // 将元素存入栈顶。
        // addElement()的实现在Vector.java中
        addElement(item)
        return item;
    }
    // pop函数:返回栈顶元素,并将其从栈中删除
    public synchronized E pop() {
        E    obj;
        int    len = size();
        obj = peek();
        // 删除栈顶元素,removeElementAt()的实现在Vector.java中
        removeElementAt(len - 1);
        return obj;
    }


    // peek函数:返回栈顶元素,不执行删除操作
    public synchronized E peek() {
        int    len = size();


        if (len == 0)
            throw new EmptyStackException();
        // 返回栈顶元素,elementAt()具体实现在Vector.java中
        return elementAt(len - 1);
    }


    // 栈是否为空
    public boolean empty() {
        return size() == 0;
    }


    // 查找“元素o”在栈中的位置:由栈底向栈顶方向数
    public synchronized int search(Object o) {
        // 获取元素索引,elementAt()具体实现在Vector.java中
        int i = lastIndexOf(o);


        if (i >= 0) {
            return size() - i;
        }
        return -1;
  

 

迭代器

 

list 遍历

if(list instanceof RandomAccess){
    for(int i = 0;i<size;i++){
        //遍历操作
    }
}
else{
    Iterator<?> iterator = list.iterator();
    while(iterator.hasNext()){
        //遍历操作
    }
}

 

迭代器位置图

迭代器指向的位置是元素之前的位置

当使用语句Iterator it=List.Iterator()时,迭代器it指向的位置是Iterator1指向的位置,当执行语句it.next()之后,迭代器指向的位置后移到Iterator2指向的位置。

因此迭代器的实现:

new Iterator() {
    // 定义一个变量指向当前迭代的节点,初始值是表头
    Node current = fisrt;
    @Override
    public Object next() {
        // 获得当前节点的内容
        Object o = current.pre;
        // 使指针指向下一个节点
        current = current.next;
        return o;
    }
    @Override
    public boolean hasNext() {
        // 判断当前节点是否为空,部位空的话表面链表还有未迭代的节点,返回true
        return current != null;
    }
};

 

 

Iterator VS ListIterator

 Iterator是一个接口,它是集合的迭代器。集合可以通过Iterator去遍历集合中的元素。Iterator提供的API接口如下:

 

 boolean hasNext():判断集合里是否存在下一个元素。如果有,hasNext()方法返回 true。

 Object next():返回集合里下一个元素。

 

 void remove():删除集合里上一次next方法返回的元素。

 

 ListIterator接口继承Iterator接口,提供了专门操作List的方法。ListIterator接口在Iterator接口的基础上增加了以下几个方法:

 

 boolean hasPrevious():判断集合里是否存在上一个元素。如果有,该方法返回 true。

 

 Object previous():返回集合里上一个元素。

 

 void add(Object o):在指定位置插入一个元素。

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值