集合学习笔记

 ArrayList

//arraylist  底层用数组实现
/*
性能分析(CRUD) :针对ArrayList的性能增删改查
1.保存操作:不高,存在数组扩容,如果把数据保存在最后一个位置,保存在第一个位置,要进行size次操作,n个元素操作n次,如果要扩容性能更低
2.删除操作:性能也不高,删除最有一个元素操作一次,第一个元素n次   后面的元素要挪动
3.
 Collection表示一组对象,他是集中,收集的意思

    操作地址
    Collection:   list有序可重复  set无序不可重复  hashset

     map 键值对   HashMAp
      接口中定义规范 一些不变的东西
     ArrayList :底层实现是数组。所以查询快,修改,插入,删除慢  线程不安全的
     Linkedlist : 底层实现是链表 所以,查询慢。修改,插入,删除快。 线程不安全的
     vector: 线程安全的  底层也是数组实现的,所以速度慢

public static <T> void test(T a){
}//extends  <=
public static <T extends Closeable> void test(T... a)

 */

List
• 特点:有序 不唯一(可重复)
• ArrayList 线性表中的顺序表
• 在内存中分配连续的空间,实现了长度可变的数组
• 优点:遍历元素和随机访问元素的效率比较高
• 缺点:添加和删除需大量移动元素效率低,按照内容查询效率低,


• LinkedList 线性表中双向链表
• 采用双向链表存储方式。
• 缺点:遍历和随机访问元素效率低下
• 优点:插入、删除元素效率比较高(但是前提也是必须先低效率查询才可。如果插入删除发
生在头尾可以减少查询次数)

import javax.naming.SizeLimitExceededException;
import java.util.Arrays;
/*
基于数组的一个链表
 */
public class MyArrayList {

   private   Object[] elements=null;

    private static final int DEFAUlT_INITIAL_CAPACITY=10;

     int size=0;
    public MyArrayList(){
        this(10);
    }

    public MyArrayList(int inttialCapacity) {
        if (inttialCapacity < 0) {
            throw new IllegalArgumentException("容量不能为负数");
        }
        elements = new Object[inttialCapacity];
    }

  public void insert(int index,Object object){
        if (index<0){
            throw new IllegalArgumentException("索引不能小于0");
        }
        if(index>=size-1){
          add(object);
        }
        if (index<size-1 ){

            if (size==elements.length){
                Object[] temp= Arrays.copyOf(elements,elements.length*2);//  方法巧妙  快速扩容
                elements =temp;
            }
           size++;
            for (int i = index; i < size-1; i++) {
            elements[i+1]=elements[i];
            }
            elements[index]=object;

        }
  }

    public  void add(Object playerNum){
        if (size==elements.length){

            Object[] newArray=new Object[size*2+1]; //  第一种方法
            System.arraycopy(elements,0,newArray,0,elements.length);

            for (int i = 0; i <elements.length ; i++) { //第二种方法
                newArray[i]=elements[i];}

            Object[] temp= Arrays.copyOf(elements,elements.length*2);// 第三种方法 方法巧妙  快速扩容
            elements =temp;
        }
       elements[size]=playerNum;
        size++;
    }
    public  void print(){
        if (elements==null){
            System.out.println("null");
        }
        if (size==0){
            System.out.println("[]");
        }
        StringBuilder stringBuilder=new StringBuilder(size*2+1);
        stringBuilder.append("[");
        for (int i = 0; i <size ; i++) {

            stringBuilder.append(elements[i]);
            if (i!=size-1){
                stringBuilder.append(",");
            }else {
                stringBuilder.append("]");
            }
        }
        System.out.println(stringBuilder.toString());
    }
    public  Object get(int index){
        if (index<0||index>size){
            throw new IllegalArgumentException("索引越界");
        }
        return elements[index];
    }
    public  int getIndex(Integer playerNum){
        for (int i = 0; i <size ; i++) {
            if (elements[i].equals(playerNum)){
                return i;
            }
        }
        return -1;
    }
    public  void set(int index,Integer num){
       elements[index]=num;
    }
    public  void update(Integer oldPlayerNum,Integer newPlayerNum){
        int index=getIndex(oldPlayerNum);
       elements[index]=newPlayerNum;
    }
    public  void delete(int index){
        for (int i = index; i <size-1 ; i++) {
           elements[i]=elements[i+1];
        }
        //把最后一个位置设置为null
       elements[size-1]=null;
        size--;
    }
    public  String toString(){
        if (elements==null){
            System.out.println("null");
        }
        if (size==0){
            System.out.println("[]");
        }
        StringBuilder stringBuilder=new StringBuilder(size*2+1);
        stringBuilder.append("[");
        for (int i = 0; i <size ; i++) {

            stringBuilder.append(elements[i]);
            if (i!=size-1){
                stringBuilder.append(",");
            }else {
                stringBuilder.append("]");
            }
        }
        return stringBuilder.toString();
    }

    public boolean isEmpty(){
        return size==0;
    }
    public void clear(){
        elements= new Object[DEFAUlT_INITIAL_CAPACITY];
        size=0;
    }

    protected int size() {
        return size;
    }
}

index=hash(value)
哈希算法   查询性能极高  hash(value)---哈希吗----某种映射关系----元素存储索引
 加载因子 哈希表快要装满的时候就会越来越慢  默认16个  0.75   当你加载到12时考虑扩容  哈希化 散列化
不会记录添加顺序的,不容许元素重复
public class MyLinkedList {
     Node frist;//链表的第一个节点
     Node last;//链表的最后一个节点
     int size;//链表的规模
    public void addFrist(Object element){
        Node  node =new Node(element);//需要保存的节点对象
        if (size==0){
            this.frist=node;
            this.last=node;
        }else {
           node.next = this.frist;//把之前第一个作为新增节点的下一个节点
            this.frist.prevous=node;//把新增节点作为第一个节点的上一个节点;
            this.frist=node;//把新增节点作为第一个节点
        }
        size++;
    }
    public Node search(Object element){
        //找到被删除的节点
        Node current=this.frist;
        for (int i = 0; i <size ; i++) {
            if (!current.element.equals(element)){
                if (current.next==null){
                    return null;
                }
                current=current.next;
            }
        }
        return current;
    }
    public void remove(Object element){
        //找到被删除的节点
        Node current=this.frist;
        for (int i = 0; i <size ; i++) {
            if (!current.element.equals(element)){
            if (current.next==null){
            return;
            }
            current=current.next;
            }
        }
        //删除节点
        if (current == frist){
            this.frist = current.next;
            this.frist.prevous =null;
        }else if (current==last){
            this.last=current.prevous;
            this.last.next=null;
        }else{
            current.prevous.next=current.next;
            current.next.prevous=current.prevous;
        }
        size--;
        System.out.println(current.element);

    }
    public void addLast(Object element){
        Node node =new Node(element);//需要保存的节点对象
        if (size==0){
            this.frist=node;
            this.last=node;
        }else {
            this.last.next=node;//把新增节点作为之前最后一个的下一个节点
            node.prevous=this.last;//把之前最后一个节点作为新增节点的上一个节点;
            this.last=node;//把新增节点作为最后一个节点
        }
       size++;
    }
    public String toString(){
        if (size==0){
            return "[]";
        }
        StringBuilder stringBuilder=new StringBuilder(size*2+1);
        Node  current = this.frist; //第一个节点
        stringBuilder.append("[");
        for (int i = 0; i <size ; i++) {
            stringBuilder.append(current.element);
            if (i!=size-1){
                stringBuilder.append(",");
            }else {
                stringBuilder.append("]");
            }
            current=current.next;
        }
        return stringBuilder.toString();
    }
    //链表中的每一个节点
    class Node{
        Node prevous; //上一个节点对象、
        Node next ; //下一个节点对象
        Object element; // 当前节点存储的数据

        public Node(Object element){
            this.element=element;
        }
    }
}
public class MyDeQue extends MyLinkedList
public class MyStack extends MyArrayList 

利用自己的链表实现了双向队列    利用自己底层为为数组的ArrayList实现了栈

public class MyStack extends MyArrayList {
    //后进先出  last in frist  out   lifo
    //一边封死的容器    就想桌子上的书一样
    //入栈  出栈 删除栈顶  查看栈顶
   //vector  类就是线程安全的ArrayList
   public void push(Object object){
       //入栈
       super.add(object);
   }
   //出栈
   public void pop(){
       int index =super.size()-1;
       super.delete(index);
   }
   //查询栈顶
   public Object peek(){
       int index=super.size()-1;
       return get(index);
    }

    public static void main(String[] args) {
        MyStack myStack=new MyStack();
        myStack.push(1);
        myStack.push(2);
        myStack.push(3);
        myStack.push(4);
        System.out.println(myStack);
        System.out.println(myStack.peek());
        myStack.pop();
        System.out.println(myStack.peek());
    }
}




//双向队列
public class MyDeQue extends MyLinkedList{
    public Object getFrist(){
        return this.frist.element ;
    }
    public Object getLast(){
        return this.last.element;
    }
    public void  removeFrist(){
        remove(this.frist);
    }
    public void removeLast(){
        remove(this.last);
    }
    public void addFrist(Object object){
        super.addFrist(object);

    }
    public void addLast(Object object){
        super.addLast(object);

    }
    public static void main(String[] args) {
        MyDeQue myDeQue= new MyDeQue();
        myDeQue.addFrist(1);
        myDeQue.addFrist(2);
        myDeQue.addLast(3);
        System.out.println(myDeQue.getFrist());
    }
}

 

for each源码分析

/foreach  源码分析1
        for (Object e:list) {
            System.out.println(e);
        }
        System.out.println("------");

//        foreach 编译后代码
        Object object;
        for(Iterator iterator1=list.iterator();iterator1.hasNext();){
            System.out.println(iterator1.next());
        }
//当需要边迭代集合元素,便删除集合元素时只能使用Iterator
// 不要使用集合对象的删除方法,使用迭代器中的iterator.remove方法 会从两个线程中同时删除




//Vector
     //可增长类对象数组  Vector类底层是一个Object数组,vector类中的方法支持同步 使用synchronized
     //  重点   我们发现该数组的类型是Object,集合中只能存储任意类型的对象,不能存储基本类型的值
     //    集合中存储的对象,都存储的是对象的引用,而不是对象本身
/*
• For-each循环
• 增强的for循环,遍历array 或 Collection的时候相当简便
• 无需获得集合和数组长度,无需使用索引访问元素,无需循环条件
• 遍历集合时底层调用Iterator完成操作
• For-each缺陷:
• 数组:
• 不能方便的访问下标值
• 不要在for-each中尝试对变量赋值,只是一个临时变量
• 集合:
• 与使用Iterator相比,不能方便的删除集合中的内容
• For-each总结:
• 除了简单遍历并读出其中的内容外,不建议使用增强for
*/

 泛型分析

public class PointDemo {
    public static void main(String[] args) {

//        编译前
        Point point = new Point();
        point.getX();
        Point<String> stringPoint = new Point<String>();
        stringPoint.getX();
//        编译后
        Point point1 = new Point();
        String x1 = (String) point1.getX();
//        泛型类中的泛型只能用于非静态方法,如果需要给静态方法设置泛型,使用泛型方法
//        泛型类中的泛型应该适用于整个类中的多个方法,有时候只对某一个方法设置泛型即可
//        一般地把自定义泛型作为该方法的返回类型才有意义,此时泛型类比也许是参数设置进来的

//<?>只能接收数据  不能作为添加
//泛型只能使用引用类型,不能使用基本类型
//泛型声明时字母不能使用静态属性||静态方法上
//因为不能使用在静态属性上,更不能用在全局常量上
//泛型是在使用时确定类型,静态的在编译时已经确定了类型
//接口中 泛型字母只能使用在方法中,不能使用在全局常量中
//泛型方法,只能访问对象的信息,不能修改信息
//子类与父类|接口一样使用泛型
//子类指定具体的类型
//子类与父类|接口,同时擦除类型
//子类泛型,父类|接口 擦出
//错误 不能子类擦除,父类|接口泛型
//泛型没有多态
//可以用在声明类型以及声明方法参数上,不能用在声明类上     不能在类上使用?号
//?表示类型不定,使用时确定
//extends 《= 上限 指定类型 子类 或自身
//super 》= 指定类型 父类或自身
//没有泛型数组,不能创建泛型数组
    }
}

Map应用  统计字符串字母出现次数

 public static void main(String[] args) {
        String string="saghasgdjgasjdgjagjagjsgjagjdgajsgdj";
        //字符串本质就是char[]
        char[] chars=string.toCharArray();
        Map<Character,Integer> map=new HashMap<>();
        //循环得到每一个字符
        for (char c:chars) {
            if (map.containsKey(c)){
//                当前map存在该字符  取出value值递增1
                Integer old = map.get(c);
                map.put(c,old+1);
            }else {
//                当前map不包含key字符,把该字符存储到map中,设置value为1
                map.put(c,1);
            }
        }
        System.out.println(map );
    }

 Map使用

  Map<Integer, String> map=new HashMap<>();
        map.put(1,"sss");
        map.put(2,"sssgg");
        map.put(3,"sssfff");
        map.put(4,"sssds");
        map.put(5,"sssxxs");
        //获取map中所有的value所组成的集合
        Collection<Object> collection= Collections.singleton(map.values());
        for (Object object:collection){
            System.out.println(object);
        }
        //获取map中所有的Entry
        Set<Map.Entry<Integer,String>> entries=map.entrySet();
        for (Map.Entry<Integer,String>entry:entries){
            Integer key=entry.getKey();
            String value = entry.getValue();
            System.out.println(key+"--"+value);
        }

 Comparable

实体类  java.lang.Comparable  +compare To
public class NewsItem implements java.lang.Comparable<NewsItem>
  @Override
    //降序
    public int compareTo(NewsItem o) {
        int result=0;
        result=-this.pubDate.compareTo(o.pubDate);
        if (0==result){  //时间相同
            result=this.hits-o.hits; //点击量升序
            if (0==result){
                result=-this.title.compareTo(o.title);
            }
        }

        return result;
    }

 TreeSet中的 数据 必须指定排序规则

 public static void main(String[] args) {
        Person person1 = new Person("您", 100);
        Person person2 = new Person("刘德华", 1000);
        Person person3 = new Person("梁朝伟", 10000);
        Person person4 = new Person("梁", 50);

//        依次存放到TreeSet容器中,使用排序的业务类,preson类不能转换为Comparable,使用空构造元素必须能够排序
//        元素不能排序必须提供额外的业务类 实现comparator  匿名内部类
        TreeSet<Person> persons = new TreeSet<>(new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getHandsome()-o2.getHandsome();
            }
        });//  treeSet 在添加数据时排序
        persons.add(person1);
        persons.add(person2);
        persons.add(person3);
        persons.add(person4);
        System.out.println(persons);
        //改变数据
        //person3.setHandsome(-100);2
        System.out.println(persons );
//        在添加数据时排序,数据的更改不会影响原来的顺序,不要修改数据,否则可能重复  可以使用final严格修饰
//        第二种方法   接口comparable 实现compareTo方法    业务排序
        Worker w1 = new Worker("aaaa",5000);
        Worker w2 = new Worker("bbbb",50000);
        Worker w3 = new Worker("cccc",500);
        Worker w4 = new Worker("dddd",50);
        TreeSet<Worker> workers=new TreeSet<>();
        workers.add(w1);
        workers.add(w2);
        workers.add(w3);
        workers.add(w4);
        System.out.println(workers);
    }

 

Comparator 

//业务类排序  java.util.Comparator +compare  方法
public class GoodspriceComp implements Comparator<Goods> {
    @Override
    public int compare(Goods o1, Goods o2) {  //降序排列
        return -(o1.getPrice()-o2.getPrice()>0?1:(o1.getPrice()==o2.getPrice())?0:-1);
    }
}

map 的应用 

    public static void main(String[] args) {
        List<Student> list = new ArrayList<>();
        exam(list);
        //统计
        Map<String,ClassRoom> rooms = new HashMap<String, ClassRoom>();
        count(rooms,list);
        printScore(rooms);
    }

//    打印总分与平均分
    public static void printScore(Map<String ,ClassRoom> rooms){
        Set<Map.Entry<String,ClassRoom>> entrySet=rooms.entrySet();
        Iterator<Map.Entry<String,ClassRoom>> iterator=entrySet.iterator();
        while(iterator.hasNext()){
            Map.Entry<String,ClassRoom> entry = iterator.next();
            ClassRoom room =entry.getValue();
            double avg = room.getTotal()/room.getStudents().size();
            System.out.println("班级号为:"+room.getNo()+",总分:"+room.getTotal()+",平均分"+avg);
        }
    }
    //        统计分数
    public static void count(Map<String,ClassRoom> rooms,List<Student> list){
        for (Student student:list){
            String no = student.getNo();
            double score= student.getScore();
            //根据班级编号,查看Map是否存在该班级,分拣思路
            ClassRoom room = rooms.get(no);
            if (room==null){
                room=new ClassRoom(no);
                rooms.put(no,room); }
            //存储  总分
            room.setTotal(room.getTotal()+score);
            room.getStudents().add(student); //加入学生
        }
    }
public static void exam(List<Student> list){
        list.add(new Student("a1","001",80));
        list.add(new Student("a2","001",80));
        list.add(new Student("a3","001",80));
        list.add(new Student("b1","002",85));
        list.add(new Student("c1","003",90));
        list.add(new Student("d1","004",99));
        list.add(new Student("b2","002",85));
        list.add(new Student("c2","003",90));
        list.add(new Student("d2","004",99));
        list.add(new Student("b3","002",85));
        list.add(new Student("c3","003",90));
        list.add(new Student("d3","004",99));
    }

 测试 垃圾回收

  @Test
    public  void tsetStrongRef() {
        //字符串常量池 共享(不能回收)
        String str ="xufei is very good";
        //弱引用 管理 对象
        WeakReference<String> wr= new WeakReference<String>(str);
        System.out.println("gc运行前:"+wr.get() );
        // 断开引用
        str=null;
        System.gc();//通知回收
        System.runFinalization();
        System.out.println("gc运行后:"+wr.get() );
    }
    @Test
    public void testWeakRef(){
        //字符串常量池 堆中
        String str =new String("xufei is very good");
        //弱引用 管理 对象
        WeakReference<String> wr= new WeakReference<String>(str);
        System.out.println("gc运行前:"+wr.get() );
        // 断开引用
        str=null;
        System.gc();//通知回收
        System.runFinalization();
        System.out.println("gc运行后:"+wr.get() );
    }

1.集合的引入
为什么使用集合而不是数组—Why
集合和数组相似点
都可以存储多个对象,对外作为一个整体存在
 2.数组的缺点
 长度必须在初始化时指定,且固定不变
数组采用连续存储空间,删除和添加效率低下
数组无法直接保存映射关系
 数组缺乏封装,操作繁琐

Collection 接口存储一组不唯一,无序的对象
• List 接口存储一组不唯一,有序(索引顺序)的对象
• Set 接口存储一组唯一,无序的对象
• Map接口存储一组键值对象,提供key到value的映射
• Key 唯一 无序
• value 不唯一 无序

--------------------------------------------------------------------------------

Set
• 特点:无序 唯一(不重复)
• HashSet
• 采用Hashtable哈希表存储结构(神奇的结构)
• 优点:添加速度快 查询速度快 删除速度快
• 缺点:无序
• LinkedHashSet
• 采用哈希表存储结构,同时使用链表维护次序
• 有序(添加顺序)
• TreeSet
• 采用二叉树(红黑树)的存储结构
• 优点:有序 查询速度比List快(按照内容查询)
• 缺点:查询速度没有HashSet快

--------------------------------------------------------------------------------

Vector
• 实现原理和ArrayList相同,功能相同,都是长度可变的数组结构,很多情况下可以互用
• 两者的主要区别如下
• Vector是早期JDK接口,ArrayList是替代Vector的新接口
• Vector线程安全,效率低下;ArrayList重速度轻安全,线程非安全
• 长度需增长时,Vector默认增长一倍,ArrayList增长50%
• Hashtable类
• 实现原理和HashMap相同,功能相同,底层都是哈希表结构,查询速度快,很多情况下可互用
• 两者的主要区别如下
• Hashtable是早期JDK提供的接口,HashMap是新版JDK提供的接口
• Hashtable继承Dictionary类,HashMap实现Map接口
• Hashtable线程安全,HashMap线程非安全
• Hashtable不允许null值,HashMap允许null值

--------------------------------------------------------------------------------

早期集合类Vector、Hashtable:线程安全的
• 是怎么保证线程安排的,使用synchronized修饰方法
• 为了提高性能,使用ArrayList、HashMap替换,线程不安全,但是性能好。使用ArrayList、
HashMap,需要线程安全怎么办呢?
• 使用Collections.synchronizedList(list);Collections.synchronizedMap(m);解决
• 底层使用synchronized代码块锁
• 虽然也是锁住了所有的代码,但是锁在方法里边,并所在方法外边性能可以理解为稍有提高吧。
毕竟进方法本身就要分配资源的
• 在大量并发情况下如何提高集合的效率和安全呢?
• 提供了新的线程同步集合类,委员java.util.concurrent包下,使用Lock锁
• ConcurrentHashMap、CopyOnWriteArrayList 、CopyOnWriteArraySet:
• 注意 不是CopyOnWriteHashSet

--------------------------------------------------------------------------------

集合和数组的比较
• 数组不是面向对象的,存在明显的缺陷,集合完全弥补了数组的一些缺点,比数组更灵活更实
用,可大大提高软件的开发效率而且不同的集合框架类可适用于不同场合。具体如下:
• 1 : 数组容量固定且无法动态改变,集合类容量动态改变。
• 2:数组能存放基本数据类型和引用数据类型的数据,而集合类中只能放引用数据类型的数据。
• 3:数组无法判断其中实际存有多少元素,length只告诉了array容量;集合可以判断实际存有多
少元素,而对总的容量不关心
• 4:集合有多种数据结构(顺序表、链表、哈希表、树等)、多种特征(是否有序,是否唯一)、
不同适用场合(查询快,便于删除、有序),不像数组仅采用顺序表方式
• 5:集合以类的形式存在,具有封装、继承、多态等类的特性,通过简单的方法和属性调用即可
实现各种复杂操作,大大提高软件的开发效

--------------------------------------------------------------------------------

ArrayList和LinkedList 的联系和区别
• 联系:
• 都实现了List接口
• 有序 不唯一(可重复)
• ArrayList
• 在内存中分配连续的空间,实现了长度可变的数组
• 优点:遍历元素和随机访问元素的效率比较高
• 缺点:添加和删除需大量移动元素效率低,按照内容查询效率低,
• LinkedList
• 采用链表存储方式。
• 缺点:遍历和随机访问元素效率低下
• 优点:插入、删除元素效率比较高(但是前提也是必须先低效率查询才可。如果插入删除发生在头尾
可以减少查询次数)

--------------------------------------------------------------------------------
Vector和ArrayList的联系和区别
• 实现原理相同,功能相同,都是长度可变的数组结构,很多情况下可以互用
• 两者的主要区别如下
• Vector是早期JDK接口,ArrayList是替代Vector的新接口
• Vector线程安全,效率低下;ArrayList重速度轻安全,线程非安全
• 长度需增长时,Vector默认增长一倍,ArrayList增长50%

--------------------------------------------------------------------------------

HashMap和Hashtable的联系和区别
• 实现原理相同,功能相同,底层都是哈希表结构,查询速度快,在很多情况下可以互用
• 两者的主要区别如下
• Hashtable是早期JDK提供的接口,HashMap是新版JDK提供的接口
• Hashtable继承Dictionary类,HashMap实现Map接口
• Hashtable线程安全,HashMap线程非安全
• Hashtable不允许null值,HashMap允许null值

--------------------------------------------------------------------------------

Collection和Collections的区别
• Collection是Java提供的集合接口,存储一组不唯一,无序的对象。它有两个子接口
List和Set。
• Java中还有一个Collections类,专门用来操作集合类 ,它提供一系列静态方法实现对
各种集合的搜索、排序、线程安全化等操作。

--------------------------------------------------------------------------------

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值