JAVA-ArrayList 源码学习-201805

JAVA-ArrayList 源码学习-201805
(基于jdk 9源码)
一.ArrayList简介

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

  List 接口的大小可变数组的实现类。实现了所有List的方法,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。(此类大致上等同于 Vector 类,除了此类是不同步的。)

二.源码学习
/**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;

    1.DEFAULT_CAPACITY默认初始容量为10.

 /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    2.不同的空数组对应不同的构造函数

transient Object[] elementData;

    3.缓冲数组elementData:用于缓存数组,该数组长度即是ArrayList的容量capacity。

    此处使用transient的原因是:ArrayList的序列化使用本类的private void writeObject/readObject (java.io.ObjectOutputStream s)方法。elementData数组中会有扩容后产生的空余容量,非最简。

    writeObject方法

/**
     * Saves the state of the {@code ArrayList} instance to a stream
     * (that is, serializes it).
     *
     * @param s the stream
     * @throws java.io.IOException if an I/O error occurs
     * @serialData The length of the array backing the {@code ArrayList}
     *             instance is emitted (int), followed by all of its elements
     *             (each an {@code Object}) in the proper order.
     */
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();            //将本类中非transient非static的字段写入ObjectOutputStream s

        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);            //将size字段写入,这里再写一次,按照注释:保持与clone()兼容[与以前版本clone()相容?]

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);    //将size内的elementData[i]写入,空余容量不写入
        }

        if (modCount != expectedModCount) {            //检查是否存在并发修改
            throw new ConcurrentModificationException();
        }
    }

    注:注意写入顺序,defaultWriteObject()一般写在第一个,原因是为了更好的兼容性。当一个类的新版本添加一个非transient字段时,若使用老版本class去反序列化新版本对象时,新加的字段将被忽略;若使用新版本class去反序列化老版本对象时,新加的字段将被赋默认值(Object:null,boolean:false,int:0...)。相应地,defaultReadObject()也一般写在第一位。

private int size;

   4. size:ArrayList中元素的个数。

public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
  5.  构造方法1:public ArrayList(int initialCapacity), initialCapacity自定义的初始容量
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
   6. 构造方法2:public ArrayList(), 初始容量为0
public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();    //将c转成数组并赋给elementData
        if ((size = elementData.length) != 0) {    //将elementData的length赋给size,在size!=0时,检验注释中提到的bug;size=0时,赋空数组
            // defend against c.toArray (incorrectly) not returning Object[]
            // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);    //若出现toArray不返回Object[]的bug,则通过
        } else {                                                                    //Arrays.copyOf()重新复制给elementData
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

   7. 构造方法3:public ArrayList(Collection<? extends E> c) 通过已有Collection c重构ArrayList。先把c通过toArray()转成数组并赋值给缓冲数组elementData,再把elementData的长度赋值给size。

/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        modCount++;        //结构性修改次数统计(包括add/remove...,不包括更改元素的值set),用于检测并发修改异常
        add(e, elementData, size);    //主要调用下面的私有方法实现功能
        return true;    //永远返回true
    }
/**
     * This helper method split out from add(E) to keep method
     * bytecode size under 35 (the -XX:MaxInlineSize default value),    //注释-要分开写一个调用方法的原因:貌似涉及jvm性能调优[勉强看看]
     * which helps when add(E) is called in a C1-compiled loop.         //把add(E)的字节码压缩到35个(MaxInlineSize :一行最多35个)
*/                                                                //这样可以用C1-编译器[即时]将字节码编译成本地代码,进行简单优化。
private void add(E e, Object[] elementData, int s) { if (s == elementData.length)             elementData = grow();                //当原元素个数=容量时,就要扩容,使用grow()方法,扩容50% elementData[s] = e;                        //扩容完毕后,将e赋给第size个元素,size+1 size = s + 1; }    8. public boolean add(E e)-在列尾增加元素

    grow()方法

 /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     * @throws OutOfMemoryError if minCapacity is less than zero
     */
    private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));  //还是Arrays.copyOf方法,这里传入size+1作为最小容量参数minCapacity
    }                                                                  //传入newCapacity()方法

    private Object[] grow() {
        return grow(size + 1);
    }

    newCapacity()方法

/**
     * Returns a capacity at least as large as the given minimum capacity.
     * Returns the current capacity increased by 50% if that suffices.
     * Will not return a capacity greater than MAX_ARRAY_SIZE unless
     * the given minimum capacity is greater than MAX_ARRAY_SIZE.
     *
     * @param minCapacity the desired minimum capacity
     * @throws OutOfMemoryError if minCapacity is less than zero
     */
    private int newCapacity(int minCapacity) {
        // overflow-conscious code                    //考虑溢出
        int oldCapacity = elementData.length;           //旧容量
        int newCapacity = oldCapacity + (oldCapacity >> 1);    //new=old+old/2 [右移1]
        if (newCapacity - minCapacity <= 0) {                        //当newCapacity<=minCapacity时,返回minCapacity
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)    //若elementData为空,则返回默认容量10,与minCapacity间的最大值;
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow                        //若minCapacity<0,则抛出内存溢出Error:考虑前面代入的参数size+1,若
                throw new OutOfMemoryError();                       //size取int的最大值2^31-1,则此时minCapacity=size+1=-2^31;即若超限
            return minCapacity;                                    //符号位必然为1,此时为负数。
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)            //当newCapacity>minCapacity时,若newCapacity<=MAX_ARRAY_SIZE [2^31-9],
            ? newCapacity                                     //返回newCapacity;否则调用hugeCapacity(minCapacity);
            : hugeCapacity(minCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow            //还是先验证是否溢出
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE)        //若minCapacity>2^31-9,则返回int的最大值2^31-1;否则,返回2^31-9.
            ? Integer.MAX_VALUE
            : MAX_ARRAY_SIZE;
    }
    补:MAX_ARRAY_SIZE
/**
     * The maximum size of array to allocate (unless necessary).        //jvm留了8个byte给数组的 header words头信息,具体参看:
     * Some VMs reserve some header words in an array.    //https://www.ibm.com/developerworks/java/library/j-codetoheap/index.html
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    即一般地,若ArrayList中没有元素且未指定初始容量,add第一个元素时,将扩容至10;之后,若元素个数=容量,则扩容当前容量的50%,再add; 若元素个数<容量,则直接add

    9.再看另一个add(int index,E element)-指定位置添加

public void add(int index, E element) {
        rangeCheckForAdd(index);        //先检查index有无数组越界,若有,则抛出越界异常
        modCount++;                    //结构性修改+1
        final int s;
        Object[] elementData;
        if ((s = size) == (elementData = this.elementData).length)    //若容量=元素个数,扩容
            elementData = grow();
        System.arraycopy(elementData, index,
                         elementData, index + 1,            //使用System.arraycopy方法将index位置以后(包括index)的元素全部后移一位
                         s - index);
        elementData[index] = element;
        size = s + 1;
    }

    public static native void arraycopy(Object src,  int  srcPos, Object dest, int destPos, int length)

    用法:src数组位置从srcPos到srcPos+length-1的元素按序赋值给dest数组destPos到desPos+length-1位置

int[] a= {14,81,97,52,57,44};
		int[] b= {0,1,0,1,0,1};
		System.arraycopy(a, 2, b, 3, 2);
		System.out.println("a"+Arrays.toString(a));
		System.out.println("b"+Arrays.toString(b));
    结果:a[14, 81, 97, 52, 57, 44]           b[0, 1, 0, 97, 52, 1]

/**
     * Trims the capacity of this <tt>ArrayList</tt> instance to be the
     * list's current size.  An application can use this operation to minimize
     * the storage of an <tt>ArrayList</tt> instance.
     */
    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }
    10.public void trimToSize()
        用于去除空余容量,使容量=元素个数。
/**
     * Trims the capacity of this <tt>ArrayList</tt> instance to be the
     * list's current size.  An application can use this operation to minimize
     * the storage of an <tt>ArrayList</tt> instance.
     */
    public void trimToSize() {
        modCount++;                    //结构性修改+1
        if (size < elementData.length) {
            elementData = (size == 0)                //当size<capacity时,若elementData为空,则赋空数组;
              ? EMPTY_ELEMENTDATA                    //若不为空,则使用Arrays.copyOf将容量控制为size
              : Arrays.copyOf(elementData, size);
        }
    }
    11.public int size()-返回size属性值

    12.public boolean isEmpty()- return size==0-判断list内元素个数是否为空

    13.public int indexOf(Object o)-判断Object o在list中第一次出现的位置

         public int lastIndexOf(Object o)-判断Object o在list中最后出现的位置[从后向前遍历]

/**
     * Returns the index of the first occurrence of the specified element
     * in this list, or -1 if this list does not contain the element.
     * More formally, returns the lowest index <tt>i</tt> such that
     * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,
     * or -1 if there is no such index.
     */
    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)                //从0开始循环遍历得到o第一次出现的位置,分o==null和o!=null两种情况
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

    14.public boolean contains(Object o)--判断list中是否包含o

/**
     * Returns <tt>true</tt> if this list contains the specified element.
     * More formally, returns <tt>true</tt> if and only if this list contains
     * at least one element <tt>e</tt> such that
     * <tt>(o==null ? e==null : o.equals(e))</tt>.
     *
     * @param o element whose presence in this list is to be tested
     * @return <tt>true</tt> if this list contains the specified element
     */
    public boolean contains(Object o) {
        return indexOf(o) >= 0;            //这里直接调用indexOf(o),若o在list内,则indexOf(o)>=0,返回true;反之,返回false;
    }

    15.public E get(int index)-取list中指定index的元素

public E get(int index) {
        rangeCheck(index);    //    越界检查

        return elementData(index);        //直接返回elementData数组中指定index的元素
    }
    16.public E set(int index,E element)--改变指定index元素的值,返回原值
public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;        //用elementData数组直接改,返回原值
        return oldValue;
    }

    17.toArray方法--把list重新转成数组

@SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:        //当size>数组a的长度,使用Arrays.copyOf复制list中所有元素
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());    //到以a.class创建长度为size的新数组,返回该数组。
        System.arraycopy(elementData, 0, a, 0, size);                    //若size<=a.length,则把elementData中所有元素赋值给a数组0到
        if (a.length > size)                                             //size-1位置,并把a[size]设为null
            a[size] = null;                                            //根据注释,设为null,可以便于统计list的长度......
        return a;
    }
public Object[] toArray() {
        return Arrays.copyOf(elementData, size);                //还是用Arrays.copyOf返回数组
    }

    18.remove方法-删除第一次出现的该元素

/**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If the list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * <tt>i</tt> such that
     * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>
     * (if such an element exists).  Returns <tt>true</tt> if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return <tt>true</tt> if this list contained the specified element
     */
    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)                //从0开始遍历list,分null和非null两种情况,具体实现调用fastRemove
                if (elementData[index] == null) {                     //若list中有该元素,可以删除,则返回true;否则返回false
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

        fastRemove()方法

private void fastRemove(int index) {
        modCount++;                            //结构性修改+1
        int numMoved = size - index - 1;          //需要移动的元素个数:index之后的元素全部向前移1位
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work    //把elementData[size-1]设为null
    }
    public E remove(int index)-按index删除,实现和fastRemove差不多。

    19.clear方法-清除list内全部元素

public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)        //遍历,把所有元素都设为null,size=0
            elementData[i] = null;

        size = 0;
    }

    20.ensureCapacity(int minCapacity)--手动设置容量

/**
     * Increases the capacity of this {@code ArrayList} instance, if
     * necessary, to ensure that it can hold at least the number of elements
     * specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    public void ensureCapacity(int minCapacity) {
        if (minCapacity > elementData.length
            && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA       //只有在minCapacity>现有容量,且若原数组为空,minCapacity>10时
                 && minCapacity <= DEFAULT_CAPACITY)) {                //才会执行扩容方法grow(minCapacity)
            modCount++;                                        //结构性修改+1
            grow(minCapacity);
        }
    }

    扩容后大小:一般地,取原容量的1.5倍与自设容量minCapacity之间的大的一个。因此,若要自定容量,最好大于1.5*原容量

    下面测试一下在大容量情况下 预设容量与不预设之间的差异

int n=10000000;
		ArrayList<Integer> list1=new ArrayList<>();
		ArrayList<Integer> list2=new ArrayList<>();
		long t1=System.currentTimeMillis();
		list1.ensureCapacity(n);
		for(int i=0;i<n;i++) {
			list1.add(i);
		}
		long t2=System.currentTimeMillis();
		for(int i=0;i<n;i++) {
			list2.add(i);
		}
		long t3=System.currentTimeMillis();
		System.out.println("预设. "+(t2-t1)+"ms");
		System.out.println("不设. "+(t3-t2)+"ms");

    结果:预设. 293ms    不设. 454ms

    21.addAll方法-将一个collection添加入原list指定位置

/**
     * Inserts all of the elements in the specified collection into this
     * list, starting at the specified position.  Shifts the element
     * currently at that position (if any) and any subsequent elements to
     * the right (increases their indices).  The new elements will appear
     * in the list in the order that they are returned by the
     * specified collection's iterator.
     *
     * @param index index at which to insert the first element from the
     *              specified collection
     * @param c collection containing elements to be added to this list
     * @return {@code true} if this list changed as a result of the call
     * @throws IndexOutOfBoundsException {@inheritDoc}
     * @throws NullPointerException if the specified collection is null
     */
    public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);        //越界检查

        Object[] a = c.toArray();        //待添加collection c转成数组
        modCount++;                    //结构性修改+1
        int numNew = a.length;
        if (numNew == 0)                //若c为空,返回false
            return false;
        Object[] elementData;
        final int s;
        if (numNew > (elementData = this.elementData).length - (s = size))    //若空余容量<待添加数组的元素个数,则扩容
            elementData = grow(s + numNew);

        int numMoved = s - index;        //原数组中需要移动的元素个数:从index到s
        if (numMoved > 0)
            System.arraycopy(elementData, index,            //将原数组index到index+numnew-1腾空,原index到size的元素全部向后移
                             elementData, index + numNew,
                             numMoved);
        System.arraycopy(a, 0, elementData, index, numNew);    //再把a数组的元素全部填充到原index到index+numnew-1的位置
        size = s + numNew;                                    //返回true
        return true;
    }
    public boolean addAll(Collection<? extends E> c)方法和以上方法思路基本一致,只是全在队尾加入。

    22.removeRange(int from,int to)--删除从from到to的元素

/**
     * Removes from this list all of the elements whose index is between
     * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive.
     * Shifts any succeeding elements to the left (reduces their index).
     * This call shortens the list by {@code (toIndex - fromIndex)} elements.
     * (If {@code toIndex==fromIndex}, this operation has no effect.)            //注释:若from==to,则不产生变化    
     *
     * @throws IndexOutOfBoundsException if {@code fromIndex} or
     *         {@code toIndex} is out of range
     *         ({@code fromIndex < 0 ||
     *          toIndex > size() ||
     *          toIndex < fromIndex})
     */
    protected void removeRange(int fromIndex, int toIndex) {
        if (fromIndex > toIndex) {
            throw new IndexOutOfBoundsException(                //检查两个坐标是否正确,不正确则抛出异常
                    outOfBoundsMsg(fromIndex, toIndex));
        }
        modCount++;                        //   结构性修改+1
        shiftTailOverGap(elementData, fromIndex, toIndex);    //调用下面的函数shiftTailOverGap()实现功能
    }

    /** Erases the gap from lo to hi, by sliding down following elements. */
    private void shiftTailOverGap(Object[] es, int lo, int hi) {
        System.arraycopy(es, hi, es, lo, size - hi);                //把es中从hi到size-1的元素,按序赋值到从lo到lo+size-hi-1的元素
        for (int to = size, i = (size -= hi - lo); i < to; i++)       //再把从lo+size-hi到size-1的元素赋null
            es[i] = null;
    }
    23.removeAll方法-删除list中包含的c内元素
public boolean removeAll(Collection<?> c) {
        return batchRemove(c, false, 0, size);        //直接调下面的batchRemove(c,false ,0,size)
    }
boolean batchRemove(Collection<?> c, boolean complement,
                        final int from, final int end) {
        Objects.requireNonNull(c);                    //检查c是否为空,若为空,抛出空指针异常
        final Object[] es = elementData;
        final boolean modified;
        int r;
        // Optimize for initial run of survivors        //优化选出第一个不符合条件的值 
        for (r = from; r < end && c.contains(es[r]) == complement; r++)    //若complement=true,表示选出第一个c中不包含元素的下标;
            ;                                                            //complement=false,选出第一个list与c有交集的元素下标
        if (modified = (r < end)) {//这里若r=end,则未选到初始下标,未对list作修改,返回false;若r<end,表示选到,进入if内返回true
            int w = r++;            //第一个不符合条件的下标,可设为r0;此时令w=r0,r=r0+1;
            try {
                for (Object e; r < end; r++)      //此时r从r0+1到end-1遍历,若存在符合条件的数组元素es[r],则把按序赋给es[w],
                    if (c.contains(e = es[r]) == complement) //(w从r0开始自增),即es[r0]到es[w-1]为选出符合条件的元素覆盖第一个不符元素
                        es[w++] = e;                      //条件:若complement=true,选出c中存在的元素;false,选出c中不存在元素
            } catch (Throwable ex) {
                // Preserve behavioral compatibility with AbstractCollection,
                // even if c.contains() throws. //这里防止contains()抛出异常,并要与AbstractCollection中的removeAll/retainAll保持一致
                System.arraycopy(es, r, es, w, end - r);    //对抛出异常后的es[r]不做检验,直接放到w位以后
                w += end - r;    //结构性修改次数+r-w次 因只删了r-w个就抛出异常了
                throw ex;
            } finally {
                modCount += end - w;    //结构型修改次数+end-w 次 最后删了end-w个元素
                shiftTailOverGap(es, w, end); //System.arraycopy(es,end,es,w,size-end)把end到size的元素移到w之后,后面赋null
            }
        }
        return modified;
    }

    batchRemove方法实现过程:先选出不符合条件的第一个元素es[r0],在从r0向后遍历,取符合条件的每个元素,按序赋给从es[r0]开始的原数组元素。

    其中removeAll(Collection c)方法调用batchRemove(Collection c, false,0,size),即先选出list中第一个c中存在的元素es[r0],再从r0向后遍历,取c中不存在元素,按序赋给从es[r0]开始的原数组元素。

    24.retainAll方法-只保留list中包含的c内元素

 public boolean retainAll(Collection<?> c) {
        return batchRemove(c, true, 0, size);    //也是调用batchRemove,实现与removeAll相似
    }

参考:

https://blog.csdn.net/u011392897/article/details/57105709

https://blog.csdn.net/u012877472/article/details/50852933

https://stackoverflow.com/questions/34250207/in-java-8-why-is-the-default-capacity-of-arraylist-now-zero

https://stackoverflow.com/questions/17235877/need-of-defaultreadobject-and-defaultwriteobject

https://blog.csdn.net/u010723709/article/details/45647447

https://blog.csdn.net/tingfeng96/article/details/52261219

https://stackoverflow.com/questions/35756277/why-the-maximum-array-size-of-arraylist-is-integer-max-value-8#

https://www.ibm.com/developerworks/java/library/j-codetoheap/index.html

https://www.cnblogs.com/vinozly/p/5171227.html


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值