三、I/O

本文详细介绍了Java中的File类,包括其构造方法、常用方法,如获取文件属性、创建、删除文件等。此外,还讲解了IO流体系,如字节流、字符流、节点流和处理流,并通过实例演示了FileReader、FileWriter、FileInputStream和FileOutputStream的使用。文章还涉及到了过滤器、匿名内部类、文件加密解密、异常日志收集、Properties和序列化技术的应用。
摘要由CSDN通过智能技术生成

一、File

一、File类实现的接口

public class File extends Object implements Serializable, Comparable{
}
类的序列化由实现java.io.Serializable接口的类启用。 不实现此接口的类将不会使任何状态序列化或反序列化。 可序列化类的所有子类型都是可序列化的。 序列化接口没有方法或字段,仅用于标识可串行化的语义
public interface Comparable:该接口对实现它的每个类的对象强加一个整体排序。 这个排序被称为类的自然排序 ,类的compareTo方法被称为其自然比较方法 。
int compareTo(T o):将此对象与指定的对象进行比较以进行排序。 返回一个负整数,零或正整数,因为该对象小于,等于或大于指定对象。

二、构造方法

1、 File(File parent, String child)
从父抽象路径名和子路径名字符串创建新的 File实例。

public static void main(String[] args) throws IOException {
			File parentFile=new File("d:\\study");
			File sonFile=new File(parentFile,"\\a.txt");
			if(!sonFile.exists()){
				sonFile.createNewFile();
			}
}

2、File(String pathname)
通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。

3、File(String parent, String child)
从父路径名字符串和子路径名字符串创建新的 File实例。

三、常用方法

获取功能:
1) public String getAbsolutePath();//获取绝对路径
2)public String getPath();//获取路径
3)public String getName();//获取文件名
4)public String getParent();获取上层文件路径 若无返回null
5)public long length();获取文件长度(字节数) 。不能获取目录长度
6)public String[] list;获取指定目录下的所有文件或者w文件目录的名称数组
7)public File[] listFiles():获取指定目录下的所有文件或者文件目录的File数组
判断功能:
1)public boolean isDirectory();
2)public boolean isFile()
3)public boolean exists()
4)public boolean canRead()
5)public boolean canWrite()
6)public boolean isHidden()
创建功能:
1)public boolean createNewFile();
2)public boolean mkdir();创建文件目录 如果此文件目录不存在就不创建了,如果此文件的上层目录不存在也不创建
3)public boolean mkdirs();创建文件目录,如果此文件的上层目录不存在则一并创建
删除功能:
1)public boolean delete();//删除文件或者文件夹
【注意】
要删除一个文件目录,请注意该目录下不能包含文件h或者文件目录

public static void main(String[] args) throws IOException {
        File parentFile=new File("d:\\study");
        File sonFile=new File(parentFile,"\\a.txt");
        if(!sonFile.exists()){
            sonFile.createNewFile();
        }
        System.out.println("getAbsoluteFile:"+sonFile.getAbsoluteFile());
        System.out.println("getPath"+sonFile.getPath());
        System.out.println("getParent"+sonFile.getParent());
        System.out.println("length:"+sonFile.length());
        String[] list=parentFile.list();
        System.out.println("public String[] list:");
        for(String s:list){
            System.out.println(s);
        }
        File[] files=parentFile.listFiles();
        System.out.println("public File[] listFiles():");
        for(File file:files){
            System.out.println(file);
        }
    }

运行结果:
在这里插入图片描述

1、创建文件
		boolean result = false;
		File file = new File(filePath);
		if(!file.exists()){
			try {
				result = file.createNewFile();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
2、创建一个文件夹
		boolean result = false;
		File file = new File(directory);
		if(!file.exists()){
			result = file.mkdir();
		}
3、创建多个文件夹
		boolean result = false;
		File file = new File(“e:\\ha\ha\ha");
		if(!file.exists()){
			result = file.mkdirs();
		}
		
4、把一个文件移动到另一个目录下
		File file=new File("D:\\kk\\tt.zip");
        File newFile=new File("c:\\a.zip");
        file.renameTo(newFile);		
5、删除文件
		boolean result = false;
		File file = new File(filePath);
		if(file.exists() && file.isFile()){
			result = file.delete();
		}

6、删除文件夹
		public static void deleteDirectory(String filePath){
		File file = new File(filePath);
		if(!file.exists()){
			return;
		}
		
		if(file.isFile()){
			file.delete();
		}else if(file.isDirectory()){
			File[] files = file.listFiles();
			for (File myfile : files) {
				deleteDirectory(filePath + "/" + myfile.getName());
			}
			
			file.delete();
		}
	}

四、路径分类

1、相对路径
2、绝对路径

五、路径分隔符

windows:\
unix:/

二、IO流

一、I/O流体系

二、流的分类

按操作数据单位不同:字节流(8bit) 字符流(16bit)
按数据流的流向不同:输入流、输出流
按流的角色不同: 节点流、处理流

抽象基类					节点流(或文件流)				缓冲流(处理流的一种)
InputStream				FileInputStream						BufferedInputStream
OutputStream			FileOutputStream					BufferedOutputStream
Reader					FileReader							BufferedReader
Writer					FileWriter							BufferedWriter

在这里插入图片描述

三、FileReader

一、操作步骤

	1、File类的实例化
	2、FileReader流的实例化
	3、读入操作
	4、资源关闭

二、FileReader每次读取一个字符

 //FileReader每次读取一个字符
    public static void testFileReader()  {
        FileReader fr=null;
       try{
           //1、实例化File类对象 指明要操作的文件
           File file=new File("d:\\a.txt");
           //2、提供具体的流
           fr=new FileReader(file);
           //3、数据读入  read():返回读入的一个字符 如果读到文件末尾返回-1
           int data;
           while((data=fr.read())!=-1){
               System.out.print((char)data);
           }
       }catch (Exception e){
       }finally {
           //4、流的关闭
           try {
               fr.close();
           } catch (IOException e1) {
               e1.printStackTrace();
           }
       }
    }

三、每次读入多个字符

public static void testFileReader2(){
        FileReader fr=null;
        try{
            //1、实例化File类对象 指明要操作的文件
            File file=new File("d:\\a.txt");
            //2、提供具体的流
            fr=new FileReader(file);
            //3、数据读入  read(char[] cbuf):返回每次读入的字符个数,如果到达文件末尾 返回-1
            char[] cbuf=new char[5];
            int len=0;
            while((len=fr.read(cbuf))!=-1){
                //方式一
               /*for(int i=0;i<cbuf.length;i++){   //这是一种错误的写法
                    System.out.println(cbuf[i]);
                }*/
                for(int i=0;i<len;i++){   //正确
                    System.out.println(cbuf[i]);
                }
                //方式二
                //错误  和方式一类似
               /* String str=new String(cbuf);
                System.out.println(str);*/
               String str=new String(cbuf,0,len);
                System.out.println(str);
            }
        }catch (Exception e){
        }finally {
            //4、流的关闭
            try {
                fr.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

【注意】
for(int i=0;i<cbuf.length;i++){ //这是一种错误的写法
System.out.println(cbuf[i]);
}
String str=new String(cbuf);//这也是一种错误的写法
System.out.println(str);

四、FileWriter

一、操作步骤

	1、File类的实例化
	2、FileReader流的实例化
	3、写出操作
	4、资源关闭

二、注意

	1、输出操作 d对应的File文件不存在b并不会报异常
	2、
		File对应的硬盘文件如果不存在,在输出的过程中会自动创建此文件
		File对应的文件如果存在:
			如果流使用的构造器s是:FileWriter(file,false)/FileWriter(file)   
			如果流使用的构造器s是:FileWriter(file,true)会在原有文件末尾追加
 public static void testWrite(){
        FileWriter fw=null;
        try{
            //1、实例化File类对象 指明要操作的文件
            File file=new File("d:\\a.txt");
            //2、提供具体的流
            fw=new FileWriter(file,true);
            //3、数据写出  read():返回读入的一个字符 如果读到文件末尾返回-1
           fw.write("dsdf");

        }catch (Exception e){
        }finally {
            //4、流的关闭
            try {
                fw.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

五、FileInputStream

一、常用方法

int read():
首先我们来看这个没有参数的read方法,从(来源)输入流中(读取的内容)读取数
据的下一个字节到(去处)java程序内部中,返回值为0到255的int类型的值,返回
值为字符的ACSII值(如a就返回97,n就返回110).如果没有可用的字节,因为已经
到达流的末尾, -1返回的值,运行一次只读一个字节,所以经常与while((len = 
inputstream.read()) != -1)一起使用.

int read(byte[] b)
从(来源)输入流中(读取内容)读取的一定数量字节数,并将它们存储到(去处)缓冲
区数组b中,返回值为实际读取的字节数,运行一次读取一定的数量的字节数.java
会尽可能的读取b个字节,但也有可能读取少于b的字节数.至少读取一个字节第
一个字节存储读入元素b[0],下一个b[1],等等。读取的字节数是最多等于b的长
度.如果没有可用的字节,因为已经到达流的末尾, -1返回的值 ,如果b.length==0,
则返回0.

int read(byte[] b,int off, int len)
读取 len字节的数据从输入流到一个字节数组。试图读取多达 len字节,但可能读
取到少于len字节。返回实际读取的字节数为整数。 第一个字节存储读入元素
b[off],下一个b[off+1],等等。读取的字节数是最多等于len。k被读取的字节数,这
些字节将存储在元素通过b[off+k-1]b[off],离开元素通过b[off+len-1]b[off+k]未受
影响。read(byte[]b)就是相当于read(byte [] b , 0 , b.length).所以两者差不多.性
质一样.

public int available() throws IOException  可以取得输入文件的大小。
public void close() throws IOException  关闭输入流
FileInputStream fis=new FileInputStream("c://a.txt");
        while(true){
            byte b=(byte)fis.read();
            if(b==-1){
                break;
            }
            System.out.println((char)b);
        }
public static void main(String args[]) throws Exception{	// 异常抛出,不处理
		// 第1步、使用File类找到一个文件
		File f= new File("d:" + File.separator + "test.txt") ;	// 声明File对象
		// 第2步、通过子类实例化父类对象
		InputStream input = null ;	// 准备好一个输入的对象
		input = new FileInputStream(f)  ;	// 通过对象多态性,进行实例化
		// 第3步、进行读操作
		byte b[] = new byte[1024] ;		// 所有的内容都读到此数组之中
		int len = input.read(b) ;		// 读取内容
		// 第4步、关闭输出流
		input.close() ;						// 关闭输出流\
		System.out.println("读入数据的长度:" + len) ;
		System.out.println("内容为:" + new String(b,0,len)) ;	// 把byte数组变为字符串输出
	}
	System.out.println("内容为:" + new String(b)) ;	// 把byte数组变为字符串输出
	System.out.println("内容为:" + new String(b,0,len));
}
public class InputStreamDemo03{
	public static void main(String args[]) throws Exception{	// 异常抛出,不处理
		// 第1步、使用File类找到一个文件
		File f= new File("d:" + File.separator + "test.txt") ;	// 声明File对象
		// 第2步、通过子类实例化父类对象
		InputStream input = null ;	// 准备好一个输入的对象
		input = new FileInputStream(f)  ;	// 通过对象多态性,进行实例化
		// 第3步、进行读操作
		byte b[] = new byte[(int)f.length()] ;		// 数组大小由文件决定
		int len = input.read(b) ;		// 读取内容
		// 第4步、关闭输出流
		input.close() ;						// 关闭输出流\
		System.out.println("读入数据的长度:" + len) ;
		System.out.println("内容为:" + new String(b)) ;	// 把byte数组变为字符串输出
	}
};
public static void main(String args[]) throws Exception{	// 异常抛出,不处理
		// 第1步、使用File类找到一个文件
		File f= new File("d:" + File.separator + "test.txt") ;	// 声明File对象
		// 第2步、通过子类实例化父类对象
		InputStream input = null ;	// 准备好一个输入的对象
		input = new FileInputStream(f)  ;	// 通过对象多态性,进行实例化
		// 第3步、进行读操作
		byte b[] = new byte[(int)f.length()] ;		// 数组大小由文件决定
		for(int i=0;i<b.length;i++){
			b[i] = (byte)input.read() ;		// 读取内容
		}
		// 第4步、关闭输出流
		input.close() ;						// 关闭输出流\
		System.out.println("内容为:" + new String(b)) ;	// 把byte数组变为字符串输出
	}
public static void main(String args[]) throws Exception{	// 异常抛出,不处理
		// 第1步、使用File类找到一个文件
		File f= new File("d:" + File.separator + "test.txt") ;	// 声明File对象
		// 第2步、通过子类实例化父类对象
		InputStream input = null ;	// 准备好一个输入的对象
		input = new FileInputStream(f)  ;	// 通过对象多态性,进行实例化
		// 第3步、进行读操作
		byte b[] = new byte[1024] ;		// 数组大小由文件决定
		int len = 0 ; 
		int temp = 0 ;			// 接收每一个读取进来的数据
		while((temp=input.read())!=-1){
			// 表示还有内容,文件没有读完
			b[len] = (byte)temp ;
			len++ ;
		}
		// 第4步、关闭输出流
		input.close() ;						// 关闭输出流\
		System.out.println("内容为:" + new String(b,0,len)) ;	// 把byte数组变为字符串输出
	}
	

六、FileOutputStream

常用方法

	public void close()	关闭输出流并释放与此流相关的任何系统资源
	public void flush()	刷新输出流,并强制任何缓冲的输出字节被写出
	public void write(byte[] b)	将b.length字节从指定的字节数组写入此输出流,内存写到硬盘
	public void write(byte[] b, int off, int len)	从指定的b字节数组写入len字节,从偏移量off开始输出到此输出流
	public abstract void write(int b)	将指定的字节(int 最后8个bit位)输出到流

构造方法

FileOutputStream(File file)
创建文件输出流 以写入File对象表示的文件
FileOutputStream(FileDescriptor fdObj) 
创建要写入指定文件描述符的文件输出流
该文件输出流表示与文件系统实际文件的现有连接
FileOutputStream(File file,boolean append)
创建文件输出流以写入由指定file对象标表示的文件输出流
FileOutputStream(String filename)
创建文件输出流 以写具有指定名称的文件
FileOutputStream(String filename,boolean append)
创建文件输出流以写入由指定file对象标表示的文件输出流
 public static void main(String[] args) throws IOException {
        File file=new File("c://a.txt" );
        OutputStream out=new FileOutputStream(file);
        String str="hello world";
        byte[] bytes=str.getBytes();
        out.write(bytes);

        for (int i=0;i<bytes.length;i++)
            out.write(bytes[i]);
    }
	 String str="hello world";
        byte[] bytes=str.getBytes();
// 第1步、使用File类找到一个文件
		File f= new File("d:" + File.separator + "test.txt") ;	// 声明File对象
		// 第2步、通过子类实例化父类对象
		OutputStream out = null ;	// 准备好一个输出的对象
		out = new FileOutputStream(f,true)  ;	// 此处表示在文件末尾追加内容
		// 第3步、进行写操作
		String str = "\r\nHello World!!!" ;		// 准备一个字符串
		byte b[] = str.getBytes() ;			// 只能输出byte数组,所以将字符串变为byte数组
		for(int i=0;i<b.length;i++){		// 采用循环方式写入
			out.write(b[i]) ;	// 每次只写入一个内容
		}
		// 第4步、关闭输出流
		out.close() ;						// 关闭输出流

	如果在文件操作中想换行的话,使用"\r\n" 完成

七、缓冲流(处理流)

一、作用:提高速度

八、转换流(处理流)

1、转换流:属于字符流

	InputStreamReader:将一个字节的输入流转换成字符的输入流
	OutputStreamWriter:将一个字节输出流转换成字符输出流

2、作用:提供字节流和字符流之间的转换

 //InputStreamReader使用
    public static void InputStreamReaderTest() throws IOException {
        FileInputStream fis=new FileInputStream("a.txt");
        InputStreamReader isr=new InputStreamReader(fis);//使用默认字符集
        InputStreamReader isr1=new InputStreamReader(fis,"UTF-8");//使用UTF-8字符集

        char[] cbuf=new char[10];
        int len;
        while((len=isr.read(cbuf))!=-1){
            String str=new String(cbuf,0,len);
            System.out.println(str);
        }
    }
   //InputStreamReader   OutputStreamWriter使用
    public static void InputAndOutputTest() throws IOException {
        File file1=new File("a.txt");
        File file2=new File("b.txt");
        FileInputStream fis=new FileInputStream(file1);
        FileOutputStream fos=new FileOutputStream(file2);

        InputStreamReader isr=new InputStreamReader(fis,"UTF-8");
        OutputStreamWriter osw=new OutputStreamWriter(fos,"UTF-8");

        char[] cbuf=new char[20];
        int len;
        while((len=isr.read(cbuf))!=-1){
            osw.write(cbuf,0,len);
        }
        isr.close();
        osw.close();
    }

九、标准流、打印流、数据流(处理流)

1、标准流

System.in:标准输入流 默认从键盘输入  返回InputStream
System.out:标准输出流 默认从控制台输出   返回OutputStream
通过System类的setIn(InputStream is)   setOut(OutputStream os)方式重新指定输入输出
 public static void testStandared() throws IOException {
        InputStreamReader isr=new InputStreamReader(System.in);
        BufferedReader br=new BufferedReader(isr);
        String data;
        while(true){
            data= br.readLine();
            if("e".equalsIgnoreCase(data)||"exit".equalsIgnoreCase(data)){
                System.out.println("程序结束");
                break;
            }
            String upperCase=data.toUpperCase();
            System.out.println(upperCase);
        }
        br.close();
    }

2、打印流

  public static void main(String[] args) throws IOException {
       PrintStream ps=new PrintStream("c.txt");
       ps.println("锄禾日当午");
       ps.println("汗滴禾下土");
       PrintWriter pw=new PrintWriter("c.txt");
       pw.println("锄禾日当午");
       pw.println("汗滴禾下土");
       pw.flush();

       //字节输出流转换成打印流
       FileOutputStream fos=new FileOutputStream("a.txt");
       PrintWriter pw1=new PrintWriter(fos);
       pw1.flush();//清空缓存  否则不会输出内容
       pw1.println("楚河日地那怪物");

       //缓存读取流 将字符输入流z转换为带有缓存 可以一次d读取一行的缓存字符d读取流
        FileReader fw=new FileReader("c.txt");
        BufferedReader br=new BufferedReader(fw);
        String text=br.readLine();
        System.out.println(text);
    }

三、过滤器

方式一

public class test {
    public static void main(String[] args) {
        File e=new File("D:\\");
        listFiles(e);
    }
    public static void listFiles(File file){
        FileFilter fileter=new AVIFileFilter();
        File[] files=file.listFiles(fileter);
        if(files!=null&&files.length>0)
        for(File f:files){
            if(f.isDirectory()){
                listFiles(f);
            }else {
                System.out.println("发现一个avi:"+f.getAbsolutePath());
            }
        }
    }
    static class  AVIFileFilter implements FileFilter{
        @Override
        public boolean accept(File filename){
            if(filename.getName().endsWith(".avi")||filename.isDirectory()){
                return true;
            }
            return false;
        }
    }
}

方式二 匿名内部类

public class test {
    public static void main(String[] args) {
        File e=new File("D:\\");
        listFiles(e);
    }
    public static void listFiles(File file){
        FileFilter fileter=new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                if(pathname.getName().endsWith(".avi")||pathname.isDirectory()){
                    return true;
                }
                return false;
            }
        };
        File[] files=file.listFiles(fileter);
        if(files!=null&&files.length>0)
        for(File f:files){
            if(f.isDirectory()){
                listFiles(f);
            }else {
                System.out.println("发现一个avi:"+f.getAbsolutePath());
            }
        }
    }
}
public class test {
//优化    
public static void main(String[] args) {
        File e=new File("D:\\");
        listFiles(e);
    }
    public static void listFiles(File file){
        File[] files=file.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                if(pathname.getName().endsWith(".avi")||pathname.isDirectory()){
                    return true;
                }
                return false;
            }
        });
        if(files!=null&&files.length>0)
        for(File f:files){
            if(f.isDirectory()){
                listFiles(f);
            }else {
                System.out.println("发现一个avi:"+f.getAbsolutePath());
            }
        }
    }
}

四、匿名内部类

1、总结:

匿名内部类也就是没有名字的内部类
正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写
但使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口

2、未使用匿名内部类

abstract class Person {
    public abstract void eat();
}
 
class Child extends Person {
    public void eat() {
        System.out.println("eat something");
    }
}
 
public class Demo {
    public static void main(String[] args) {
        Person p = new Child();
        p.eat();
    }
}
运行结果:eat something
可以看到,我们用Child继承了Person类,然后实现了Child的一个实例,将其向上转型为Person类的引用
但是,如果此处的Child类只使用一次,那么将其编写为独立的一个类岂不是很麻烦?

3、在抽象类上的使用匿名内部类

abstract class Person {
    public abstract void eat();
}
 
public class Demo {
    public static void main(String[] args) {
        Person p = new Person() {
            public void eat() {
                System.out.println("eat something");
            }
        };
        p.eat();
    }
}

4、在接口上使用匿名内部类


interface Person {
    public void eat();
}
 
public class Demo {
    public static void main(String[] args) {
        Person p = new Person() {
            public void eat() {
                System.out.println("eat something");
            }
        };
        p.eat();
    }
}

五、文件加密和解密

public static void main(String[] args) throws IOException {
        System.out.println("请输入文件存储的全路径:");
        Scanner input=new Scanner(System.in);
        String fileName=input.nextLine();
        //源文件
        File oldFile=new File(fileName);
        //加密存储的新文件 mi-a.png
        File newFile=new File(oldFile.getParentFile(),"mi-"+oldFile.getName());

        FileInputStream fis=new FileInputStream(oldFile);
        FileOutputStream fos=new FileOutputStream(newFile);
        while(true){
            int b=fis.read();
            if(b==-1){
                break;
            }
            //任何数据^相同数据两句 结果就是其本身
            fos.write(b^10);
        }
    }

六、收集异常日志

public static void main(String[] args) throws IOException {
       try{
           String s=null;
           s.toString();
       }catch (Exception e){
           PrintWriter pw=new PrintWriter("c://bug.txt");
           SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm");
           pw.println(sdf.format(new Date()));
           e.printStackTrace(pw);
           pw.close();
       }
    }

七、properties

写入
 public static void main(String[] args) throws IOException {
       //properties文件与properties类
        Properties ppt=new Properties();
        ppt.put("name","京苹果");
        ppt.put("info","讲述了苹果终止的过程");
        FileWriter fw=new FileWriter("d://book.properties");
        ppt.store(fw,"存储的图书");//properties文件的首航注释
        fw.close();
    }
	      
        读出:
  public static void main(String[] args) throws IOException {
        Properties ppt=new Properties();
        Reader r=new FileReader("d://book.properties");
        ppt.load(r);

        System.out.println(ppt.getProperty("name"));
        System.out.println(ppt.getProperty("info"));
    }

八、序列化技术

public static void main(String[] args) throws IOException, ClassNotFoundException {
       //序列化技术  将对象序列化为文件
        Book b=new Book("金苹果","描述了苹果终止的过程");
        ObjectOutputStream  oos=new ObjectOutputStream(new FileOutputStream("d://book.txt"));
        oos.writeObject(b);
        oos.close();
        //反序列化 将文件变成对象
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream("d://book.txt"));
        Object o=ois.readObject();
        System.out.println(o);
    }
    static class  Book implements Serializable{
        private String name;
        private String info;

        public Book(String name, String info) {
            this.name = name;
            this.info = info;
        }
    }

九、任务源码

I/O任务源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值