java输入输出流课程_输入输出--Java IO流

File类:

写在学习之前,在之前我们操作系统中的文件都是通过可视化界面,接下来关于IO学习中的示例,是使用代码去针对文件进行操作

File类构造函数

API

File(String pathname)

根据一个路径得到File对象

File(String parent, String child)

根据一个目录和一个子文件/目录得到File对象

File(File parent, String child)

根据一个父File对象和一个子文件/目录得到File对象

代码示例

需求:创建一个File类型对象,路径分为相对路径和绝对路径,判断该对象对应的文件/路径在电脑中是否存在

exists()用于判断File对象是否存在

File f1 = new File(相对路径/绝对路径);

System.out.println(f1.exists());

File f2 = new File(父路径,子路径/文件名);

System.out.println(f2.exists());

File f3 = new File(File类型对象,子路径/文件名);

System.out.println(f3.exists());

课程小结

1. File类型对象的创建有几种方式?

File类的创建功能

API

boolean mkdir()

创建文件夹,如果存在这样的文件夹,就不创建了,路径不存在返回flase

boolean mkdirs()

创建文件夹,如果父文件夹不存在,会帮你创建出来

boolean createNewFile()

创建文件,路径不存在抛出异常,如果存在这样的文件,就不创建了

代码示例

注意事项:若创建文件或者文件夹忘了写盘符路径,那么默认在项目路径下

需求:指定路径的File对象,mkdir创建文件夹,路径存在和路径不存在

需求:指定路径的File对象,mkdirs创建文件夹,路径存在和路径不存在

需求:指定路径的File对象,createNewFile创建文件,路径存在和路径不存在

课程小结

1. 单级文件夹创建和多级文件夹创建

2. 创建文件的操作

File类的获取功能

API

String getAbsolutePath()

获取文件的绝对路径

File getAbsoluteFile()

获取文件的绝对路径

String getPath()

获取文件的相对路径

String getName()

获取名称

String[] list()

获取指定目录下的所有文件或者文件夹的名称数组

File[] listFiles()

获取指定目录下的所有文件或者文件夹的File数组

File[] listRoots()

获取电脑中的盘符

String[] list (FilenameFilter filter)

获取指定目录下被FilenameFilter筛选后的文件,返回String类型数组

注意:FilenameFilter实现类中的accept(File file,String name)中两个参数,第一个参数为File对象可以表示为一个路径,第二个参数为该路径下的文件名

File[] list (FilenameFilter filter)

获取指定目录下被FilenameFilter筛选后的文件,返回String类型数组

File[] listFiles (FileFilter filter)

获取指定目录下被FileFilter筛选后的文件,返回File类型数组

注意:FileFilter实现类中的accept(File file)只有一个参数,该参数指代的是一个文件的路径,和FilenameFilter中的区别在于,FilenameFilter中两个参数合在一起才能指定一个文件,而FileFilter则没有将路径和文件名分开,通过观察两个接口的名称也能看出二者的区别

代码示例

思考&讨论:

如果当前只需要显示指定类型的文件,怎么做?画图工具打开指定的文件类型

【练习】自定义文件过滤

File f1 = new File("D:\\work\\STS\\JavaSE-Day23-IO\\2.txt\\aaa");

String suffix="txt";

//3.获取目录中存在的文件列表

String[] list = f1.list();

//4.针对获取到的文件进行一个后缀的判定,符合要求的文件输出

for (String string : list) {

if (string.endsWith(suffix)) {

System.out.println(string);

}

}

【扩展练习】将自定义文件过滤的方法封装为返回类型为String[]的方法

问题:

若直接定义数组返回,需要指定数组的长度,但当前获取的文件是不固定的

解决:

定义集合动态添加最终的值,最后将集合转化为数组

File f1 = new File("D:\\work\\STS\\JavaSE-Day23-IO\\2.txt\\aaa");

String suffix="txt";

//3.获取目录中存在的文件列表

String[] list = f1.list();

//4.针对获取到的文件进行一个后缀的判定,符合要求的文件添加到集合中

ArrayList arrayList = new ArrayList<>();

for (String string : list) {

if (string.endsWith(suffix)) {

//将筛选的值添加到集合中

arrayList.add(string);

}

}

//将集合中的数据转换为数组,数组的长度使用集合的长度

return arrayList.toArray(new String[arrayList.size()]);

【API中的过滤】使用list(FilenameFilter filter)、listFiles(FilenameFilter filter)、listFiles(FileFilter filter)过滤

list(FilenameFilter filter)方法代码演示:

//实现过滤接口FilenameFilter

public class MyFilenameFilter implements FilenameFilter {

@Override

public boolean accept(File dir, String name) {

System.out.println(dir+"======="+name);

return name.endsWith("txt");

}

}

//代码实现

File f1 = new File("D:\\work\\STS\\JavaSE-Day23-IO\\2.txt\\aaa");

//使用自定义的过滤器

String[] list = f1.list(new MyFilenameFilter());

for (String string : list) {

System.out.println(string);

}

list(FileFilter filter)代码演示:

//定义FileFilter实现类

public class MyFileFilter implements FileFilter {

@Override

public boolean accept(File pathname) {

System.out.println("========="+pathname);

return pathname.getName().endsWith("txt");

}

}

//代码实现

File f1 = new File("D:\\work\\STS\\JavaSE-Day23-IO\\2.txt\\aaa");

File[] listFiles = f1.listFiles(new MyFileFilter());

//过滤之后的信息

for (File file : listFiles) {

System.out.println(file);

}

课程小结

1. File类型中获取路径是根据什么形式来获取的?

2. 获取路径中存在的文件,使用的API?

3. 如何显示指定的文件?

File类的判断功能

思考&讨论:

为什么需要存在判断方法?

在IO的所有操作中,大家可以发现针对文件和文件夹的操作是不一样的,最明显的是API都不同

API

boolean exists()

判断路径表示的文件或者文件夹是否存在

boolean isDirectory()

判断当是否是一个文件夹

boolean isFile()

判断当前是否是一个文件

代码示例

【练习】判断当前File类型是否是一个文件夹,isDirectory()

【练习】判断当前File类型是否是一个文件,isFile()

课程小结

1. 判断文件夹使用的API?

2. 判断文件使用的API?

File类的删除功能

API

boolean delete()

若是文件就直接删除,若是文件夹则必须是空文件夹

代码示例

【代码练习】删除文件夹,文件夹中包含子文件

File f1 = new File("D:\\work\\STS\\JavaSE-Day23-IO\\2.txt\\aaa\\bb");

System.out.println(f1.delete());

//删除文件

File f2 = new File(f1,"cc");

System.out.println(f2.delete());

File f3 = new File(f1,"dd.txt");

System.out.println(f3.delete());

【代码练习】优化删除文件夹的代码,使用代码判定是文件夹还是文件

File f1 = new File("D:\\work\\STS\\JavaSE-Day23-IO\\2.txt\\aaa\\bb");

if (f1.isDirectory()) {

//如果File对象是文件夹

File[] listFiles = f3.listFiles();

for (File file : listFiles) {

file.delete();

}

}

// 如果File对象是文件或者是空文件夹就直接删除

F1.delete();

思考&讨论:

删除文件夹的时候,如果文件夹中还有嵌套的子文件夹,那么应该如何删除?

【代码练习】若文件夹中还嵌套的有其他不为空的文件夹呢?

File f1 = new File("D:\\work\\STS\\JavaSE-Day23-IO\\2.txt\\aaa\\bb");

//递归调用

aboutDelete3(f3);

public static void aboutDelete3(File f3) {

if (f3.isDirectory()) {

//如果File对象是文件夹

File[] listFiles = f3.listFiles();

for (File file : listFiles) {

aboutDelete3(file);

}

}

// 如果File对象是文件或者是空文件夹就直接删除

f3.delete();

}

递归

从前有座山,山上有座庙,庙里有个老和尚在给小和尚讲故事:从前有座山,山上有座庙,庙里有个老和尚在给小和尚讲故事:从前有座山,山上有座庙,庙里有个老和尚在给小和尚讲故事:……

递归:本质上就是方法自己调用自己

递归中处理的代码逻辑实质上是一样的,所以可以实现自己调用自己 ,但是递归不能无限制去调用,因为递归调用是无限次调用,而电脑内存是有限的,所以递归的使用需要慎重

代码示例

【演示】main方法自己调自己,出现的异常,截图

【练习】斐波拉契数列,假设有函数f(x)且f(0)=0、f(1)=1,第x个值公式为f(x)=f(x-1)+f(x-2),根据以上给出的条件实现x为任意数时的值的代码实现

public static int f(int x){

if (x == 0) {

return 0;

}else if (x == 1) {

return 1;

}

return f(x-1)+f(x-2);

}

访问文件:

字节输入流【FileInputStream】

字节输入流读取文件的实现步骤:

1. 关联实体文件,创建FileInputStream对象

2. 调用read方法

3. 关闭输入流【close】

API

1. 字节输入流对象,使用构造函数:

FileInputStream(File file)

返回一个字节输入流对象,该对象使用字节流读取文件,参数为File类型关联实体文件

FileInputStream(String name)

返回一个字节输入流对象,传入的name为路径+文件名

2. 使用字节输入流对象读取文件:

字节输入流对象在读取到最终的数据结尾时,返回-1

int read()

单字节读取文件,返回读取的下一次的数据字节数,若下一次读取没有数据则返回为-1

int read(byte[] b)

多字节读取文件,读取指定数组长度的数据字节,若下一次读取没有数据则返回为-1

多字节读取时,使用byte类型的数组作为缓冲区,可能会存在数据重复问题

int read(byte[] b,int off,int len)

多字节读取文件,读取指定数组长度的数据字节,若下一次读取没有数据则返回为-1

参数2和参数3是针对数组缓冲区进行的设置

参数off:从缓冲区数组第几个下标开始读取

参数len:从缓冲区数组中读取指定长度的数据

注意:参数2和参数3相加的值不能大于等于数组的长度,否则会引发下标异常

3. 关闭输入流对象

void close()

关闭输入流对象

代码示例

单个字节读取

FileInputStream inputStream = new FileInputStream(new File

("D:/work/STS/JavaSE-Day24-IO/1.txt"));

//System.out.println(inputStream.read());

//System.out.println((char)inputStream.read());

//循环遍历读取数据

int n;

while ((n=inputStream.read())!=-1) {

System.out.println((char)n);

}

//关闭输入流

inputStream.close();

多个字节读取,采用byte类型数组为缓冲区,若缓冲区不能被数据长度整除则可能造成数据重复输出

FileInputStream inputStream = new FileInputStream

("D:/work/STS/JavaSE-Day24-IO/1.txt");

byte[] b=new byte[4];

//将字节数组打印输出

for (byte c : b) {

System.out.println((char)c);

}

int n;

while ((n=inputStream.read(b))!=-1) {

System.out.println(new String(b));

}

//关闭输入流

inputStream.close();

多个字节读取,解决数据重复问题

FileInputStream inputStream = new FileInputStream

("D:/work/STS/JavaSE-Day24-IO/1.txt");

// 定义读取数据的缓冲区

byte[] b = new byte[4];

//循环读取并输出

int n;

while ((n = inputStream.read(b)) != -1) {

System.out.println(new String(b,0,n));

}

//关闭输入流

inputStream.close();

多个字节读取,对数组缓冲区进行设置

使用read(byte[] b,int off,int len)

参数2和参数3是针对缓冲区进行设置,相加的值不能大于等于数组长度

FileInputStream inputStream = new FileInputStream

("D:/work/STS/JavaSE-Day24-IO/1.txt");

// 定义读取数据的缓冲区

byte[] b = new byte[4];

//循环读取并输出

int n=0;

while ((n = inputStream.read(b,1,2)) != -1) {

System.out.println(new String(b));

}

//关闭输入流

inputStream.close();

课程小结

1.字节流读取文件的实现步骤?

字节输出流【FileOutputStream】

字节流输出数据的代码实现步骤:

1. 关联实体文件,创建输出流对象

2. 调用write方法

3. 关闭输出流

API

1. 字节输出流对象,构造函数:

FileOutputStream(File file)

返回一个字节输出流对象,参数为File类型关联实体文件

FileOutputStream(String name)

返回一个字节输出流对象,参数为String类型,取值为路径+文件名

向文件中设置是否追加数据:

FileOutputStream(File file, boolean append)

返回一个字节输出流对象,参数1File类型关联实体文件,参数2布尔值true代表追加

FileOutputStream(String name, boolean append)

返回一个字节输出流对象,参数1String类型关联实体文件,参数2布尔值true代表追加

2. 字节输出流对象输出数据到指定文件:

注意:

如果输出的路径不存在会抛出异常,如果输出的路径存在而文件不存在则会创建新的文件

如果不需要对输出的文件进行追加效果,那么需要在创建输出流对象的时候添加布尔值

void write(int b)

将指定字节写入文件输出流中

void write(byte[] b)

将指定的byte数组中的数据写入到文件

void write(byte[] b,int off,int len)

将指定byte数组中的数据指定起始位置和长度的数据输出到文件中

3. 关闭字节输出流对象:

void close()

关闭文件输出流

代码示例

注意事项:

如果输出的文件路径不存在,则抛出异常

如果输出的文件不存在,则自动创建新的文件

如果输出流对象没有设置追加,且输出路径和文件是一致的,则会覆盖前次的输出内容

使用FileOutputStream(File file)创建对象输出数据到文件:

FileOutputStream fos = new FileOutputStream

(new File("D:/work/STS/JavaSE-Day24-IO/2.txt"));

fos.write(97); //97 是ASCII码值,文件中写入的是a

fos.close();

使用FileOutputStream(String name)创建对象输出数据到文件:

FileOutputStream fos = new FileOutputStream

("D:/work/STS/JavaSE-Day24-IO/2.txt");

byte[] b="abcdefghijk".getBytes();

fos.write(b); //传入的是一个byte类型的数组

fos.close();

FileOutputStream(File file,boolean append)创建追加输出流对象:

FileOutputStream fos = new FileOutputStream

("D:/work/STS/JavaSE-Day24-IO/2.txt",true);

byte[] b="0123456789".getBytes();

fos.write(b, 2, 6);

fos.close();

FileOutputStream(String name,Boolean append)创建追加输出流对象:

FileOutputStream fos = new FileOutputStream

("D:/work/STS/JavaSE-Day24-IO/2.txt", true);

byte[] b="0123456789".getBytes();

fos.write(b, 2, 2);

fos.close();

课程小结

1. 不存在的路径中是否能将数据输出到文件中?

2. 文件若不存在,是否会自动创建?

字符编码【概述】

电脑上数据最终都是显示为二进制,但在实际生活中我们有很多种语言,比如:中文、英文、日文、韩文等。要将这些不同的文字形式显示在电脑上就出现了不同的编码,简单的讲字符编码就是指此类情况

常见的字符编码:

ASCII编码:

用来表示英文,它使用1个字节表示,其中第一位规定为0,其他7位存储数据,一共可以表示128个字符。

拓展ASCII编码:

用于表示更多的欧洲文字,用8个位存储数据,一共可以表示256个字符

GBK/GB2312/GB18030:

简称国标,表示汉字。GB2312表示简体中文,GBK/GB18030表示繁体中文,其实就是几个不同的版本而已。

Unicode编码:

包含世界上所有的字符,是一个字符集。

UTF-8:

是Unicode字符的实现方式之一,它使用1-4个字符表示一个符号,根据不同的符号而变化字节长度

ISO8859-1:

是单字节编码,向下兼容ASCII,不支持中文!

API

数据在电脑中传递时,本质上是字节,所以本次乱码问题主要讲解中文转字节

String中有对应的方法:

byte[] getBytes()

使用平台的默认字符集将此 String 编码为 byte 序列

byte[] getBytes(Charset charset)

使用指定的字符编码来编码字符串

byte[] getBytes(String charsetName)

使用指定的字符编码来编码字符串

String中有对应的构造方法:

String(byte[] bytes)

通过使用平台的默认字符集解码指定的 byte 数组

String(byte[] bytes, Charset charset)

使用指定的字符集来解码指定的byte数组

String(byte[] bytes, String charsetName)

使用指定的字符集来解码指定的byte数组

代码示例

将字符串使用ISO-8859-1编码,使用UTF-8解码

String res="我是中国人";

byte[] bytes = res.getBytes("ISO-8859-1");

System.out.println(new String(bytes,"UTF-8"));

将字符串使用UTF-8编码,使用UTF-8解码

String res="我是中国人";

byte[] bytes = res.getBytes("UTF-8");

System.out.println(new String(bytes,"UTF-8"));

字符流

字符输入流【FileReader】

字符输入流读取文件的实现步骤:

1. 关联实体文件,创建FileReader对象

2. 调用read方法

3. 关闭输入流【close】

API

1. 字符输入流对象,使用构造函数:

FileReader(File file)

返回一个字符输入流对象,该对象使用字符流读取文件,参数为File类型关联实体文件

FileReader(String name)

返回一个字符输入流对象,传入的name为路径+文件名

2. 使用字符输入流对象读取文件:

字符输入流对象在读取到最终的数据结尾时,返回-1

int read()

单字符读取文件,返回读取的下一次的数据字符数,若下一次读取没有数据则返回为-1

int read(char[] c)

多字符读取文件,读取指定数组长度的数据字符,若下一次读取没有数据则返回为-1

多字符读取时,使用byte类型的数组作为缓冲区,可能会存在数据重复问题

int read(char[] c,int off,int len)

多字符读取文件,读取指定数组长度的数据字符,若下一次读取没有数据则返回为-1

参数2和参数3是针对数组缓冲区进行的设置

参数off:从缓冲区数组第几个下标开始读取

参数len:从缓冲区数组中读取指定长度的数据

注意:参数2和参数3相加的值不能大于等于数组的长度,否则会引发下标异常

3. 关闭输入流对象

void close()

关闭输入流对象

代码示例

单个字符读取

FileReader fr = new FileReader(new File

("D:/work/STS/JavaSE-Day24-IO/1.txt"));

//System.out.println(fr.read());

//循环遍历读取数据

int n;

while ((n=fr.read())!=-1) {

System.out.println((char)n);

}

//关闭输入流

fr.close();

多个字节读取,采用byte类型数组为缓冲区,若缓冲区不能被数据长度整除则可能造成数据重复输出

FileReader fr = new FileReader("D:/work/STS/JavaSE-Day24-IO/1.txt");

char[] c=new char[4];

//将字节数组打印输出

for (char c : b) {

System.out.println((char)c);

}

int n;

while ((n=fr.read(b))!=-1) {

System.out.println(c);

}

//关闭输入流

fr.close();

多个字节读取,对数组缓冲区进行设置

使用read(char[] c,int off,int len)

参数2和参数3是针对缓冲区进行设置,相加的值不能大于等于数组长度

FileReader fr = new FileReader("D:/work/STS/JavaSE-Day24-IO/1.txt");

// 定义读取数据的缓冲区

char[] b = new char[4];

//循环读取并输出

int n=0;

while ((n = fr.read(c,1,2)) != -1) {

System.out.println(c);

}

//关闭输入流

fr.close();

字符输出流【FileWriter】

字符流输出数据的代码实现步骤:

1. 关联实体文件,创建输出流对象

2. 调用write方法

3. 关闭输出流

API

1. 字符输出流对象,构造函数:

FileWriter(File file)

返回一个字符输出流对象,参数为File类型关联实体文件

FileWriter(String name)

返回一个字符输出流对象,参数为String类型,取值为路径+文件名

向文件中设置是否追加数据:

FileWriter(File file, boolean append)

返回一个字符输出流对象,参数1File类型关联实体文件,参数2布尔值true代表追加

FileWriter(String name, boolean append)

返回一个字符输出流对象,参数1String类型关联实体文件,参数2布尔值true代表追加

2. 字符输出流对象输出数据到指定文件:

注意:

如果输出的路径不存在会抛出异常,如果输出的路径存在而文件不存在则会创建新的文件

如果不需要对输出的文件进行追加效果,那么需要在创建输出流对象的时候添加布尔值

void write(int b)

将指定字符写入文件输出流中

void write(char[] c)

将指定的char数组中的数据写入到文件

void write(char[] c,int off,int len)

将指定char数组中的数据指定起始位置和长度的数据输出到文件中

void write(String str)

将字符串输出到指定的文件中

void write(String str,int off,int len)

指定字符串的起始位置和长度,输出到指定的文件中

3. 关闭字节输出流对象:

void close()

关闭文件输出流

代码示例

注意事项:

如果输出的文件路径不存在,则抛出异常

如果输出的文件不存在,则自动创建新的文件

如果输出流对象没有设置追加,且输出路径和文件是一致的,则会覆盖前次的输出内容

使用FileWriter(File file)创建对象输出数据到文件:

FileWriter fw = new FileWriter

(new File("D:/work/STS/JavaSE-Day24-IO/2.txt"));

fw.write(97); //97 是ASCII码值,文件中写入的是a

fw.close();

使用FileWriter(String name)创建对象输出数据到文件:

FileWriter fw = new FileWriter("D:/work/STS/JavaSE-Day24-IO/2.txt");

char[] c={'我','你'};

fw.write(c); //传入的是一个char类型的数组

fw.close();

FileWriter(File file,boolean append)创建追加输出流对象:

FileWriter fw = new FileWriter

(new File("D:/work/STS/JavaSE-Day24-IO/2.txt"),true);

char[] c={'我','你'};

fw.write(c, 0, 1);

fw.close();

FileWriter(String name,Boolean append)创建追加输出流对象:

FileWriter fos = new FileWriter("D:/work/STS/JavaSE-Day24-IO/2.txt", true);

char[] c={'我','你'};

fw.write(c, 0, 1);

fw.close();

直接将字符串输出到文件中

FileWriter fw = new FileWriter("D:/work/STS/JavaSE-Day24-IO/2.txt",true);

fw.write("今晚小树林");

fw.close();

1. 转换流

电脑中任何数据都是以二进制形式存在的,文件被读取显示通过字节流读取然后转换为字符流显示,若需要将数据写入文件中,那么数据最终在文件中写入的是二进制形式,所以可以显示指定二进制流关联文件,字符流转换为字节流

字节流转字符流【InputStreamReader】

字节流转成字符流的步骤:

1. 准备一个字节流

2. 将字节流转成字符流输出

3. 关闭流

API

将字节流转换为字符流,使用InputStreamReader构造函数

InputStreamReader(InputStream in)

将字节流对象包装成一个InputStreamReader对象,默认使用本机字符编码

InputStreamReader(InputStream in,String charset)

将字节流对象包装成一个InputStreamReader对象,使用指定的编码格式

代码示例

先使用字节流读取包含中文的文件并输出

FileInputStream fis = new FileInputStream

("D:/work/STS/JavaSE-Day24-IO/src/cn/itsource/readerandwriter/ReaderAndWriterTest.java");

byte[] b=new byte[4];

int len;

StringBuilder build=new StringBuilder();

while ((len=fis.read(b))!=-1) {

build.append(new String(b));

}

System.out.println(build.toString());

fis.close();

优化字节流读取文件时可能出现的乱码问题

FileInputStream fis = new FileInputStream

("D:/work/STS/JavaSE-Day24-IO/src/cn/itsource/readerandwriter/ReaderAndWriterTest.java");

//将字节流包装为字符流

InputStreamReader isr = new InputStreamReader(fis);

char[] c=new char[4];

int len;

StringBuilder build=new StringBuilder();

while ((len=isr.read(c))!=-1) {

build.append(new String(c));

}

System.out.println(build.toString());

isr.close();

fis.close();

课程小结

1. 如何将字节流包装为字符流?

字符流转字节流【OutputStreamWriter】

字符流转字节流步骤:

1. 准备一个字符流

2. 将字符流转换为字节流

3. 关闭流

API

将字符流转换为字节流

OutputStreamWriter(OutputStream out)

将一个字符流中的数据内容写入到转换为显示指定的字节流对象

OutputStreamWriter(OutputStream out,String charset)

使用指定的编码格式将字符流转换为字节流

代码示例

字符流输出数据到文件中,使用字节流关联文件

FileOutputStream fos = new FileOutputStream

("D:/work/STS/JavaSE-Day24-IO/5.txt"); //字节流关联实体文件

OutputStreamWriter osw = new OutputStreamWriter(fos); //字符流输出

osw.write("输出中文");

osw.close();

fos.close();

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值