12.Java中的io流,NIO流,缓冲区与channel介绍与练习

File文件类

1.访问文件名相关的方法

String getName():返回此File对象所表示的文件名或路径名(如果是路径,则返回最后一级子路径名)。
String getPath():返回此File对象所对应的路径名。File getAbsoluteFile():返回此 File对象的绝对路径。
String getAbsolutePath(:返回此File对象所对应的绝对路径名。
String getParent():返回此File对象所对应目录(最后一级子目录)的父目录名。
boolean renameTo(File newName):重命名此File对象所对应的文件或目录,如果重命名成功,则返回true;否则返回false。

2.文件检测相关的方法

boolean exists():判断File对象所对应的文件或目录是否存在。
boolean can Write():判断File对象所对应的文件和目录是否可写。
boolean canRead():判断File对象所对应的文件和目录是否可读。
boolean isFile():判断File对象所对应的是否是文件,而不是目录。
boolean isDirectory():判断File对象所对应的是否是目录,而不是文件。
boolean isAbsolute():判断File对象所对应的文件或目录是否是绝对路径。

3.获取常规文件信息

long lastModified():返回文件的最后修改时间。
long length():返回文件内容的长度。

4.文件操作相关的方法

boolean createNewFile():当此File对象所对应的文件不存在时,该方法将新建一个该File对象所指定的新文件,如果创建成功则返回true;否则返回false。
boolean delete():删除File对象所对应的文件或路径。

5.目录操作相关的方法

boolean mkdir():试图创建一个File对象所对应的目录,如果创建成功,则返回 true;否则返回false。调用该方法时File对象必须对应一个路径,而不是一个文件。
String[]list():列出 File对象的所有子文件名和路径名,返回String 数组。
File[] listFiles():列出 File对象的所有子文件和路径,返回File数组。
static File[] listRoots():列出系统所有的根路径。这是一个静态方法,可以直接通过File类来调用。
import java.io.File;
import java.io.IOException;
//文件常见基础操作
public class FileMain {
	public static void main(String[] args) throws IOException {
		// 可以是目录 可以是文件
		// 1、如何构建文件对象 提供文件路径
		File file1 = new File("D:\\GZ2204\\0217");
		// 提供父路径
		String path = "D:\\GZ2204\\0217";
		File file2 = new File(path, "test.txt");

		// 提供父文件对象
		File parent = new File("D:\\GZ2204\\0217");
		File file3 = new File(parent, "test.txt");

		// 2、获取文件信息
		// System.out.println("文件的绝对路径:"+file1.getAbsolutePath());
		// System.out.println("文件的构建路径:"+file1.getPath());
		// System.out.println("文件名:"+file1.getName());
		// System.out.println("文件的内存大小:"+file1.length()+"字节");//1G=1024MB

		// 3、判断文件属性
		// System.out.println("文件是否存在:"+file2.exists());
		// System.out.println("file是否为目录:"+file1.isDirectory());
		// System.out.println("file是否为文件:"+file1.isFile());

		// 4、文件操作方法【添加、删除】
		// System.out.println(file2.createNewFile());//当文件不存在则创建、否则返回false
		// System.out.println(file1.delete());//
		// System.out.println(file1.mkdir());//只能创建单层目录,多层是不可以的
		// System.out.println(file1.mkdirs());//能创建多层目录

		// 5、文件的遍历两个方法
		// String[] files = file1.list(); //获取文件名列表
		// for (String string : files) {
		// System.out.println(string);
		// }

		// File[] files = file1.listFiles();//获取文件对象列表
		// for (File file : files) {
		// System.out.println(file.getName());
		// }


	}
}

遍历例子

如何遍历0217下的所有文件、包括子目录的文件
import java.io.File;
import java.io.IOException;	
public class FileMain {
	public static void main(String[] args) throws IOException {
		// 可以是目录 可以是文件
		// 1、如何构建文件对象 提供文件路径
		File file1 = new File("D:\\GZ2204\\0217");
		// 提供父路径
		String path = "D:\\GZ2204\\0217";
		File file2 = new File(path, "test.txt");

		// 提供父文件对象
		File parent = new File("D:\\GZ2204\\0217");
		File file3 = new File(parent, "test.txt");
		//如何遍历0217下的所有文件、包括子目录的文件
		printFile(file1);
	}
}
	// 定义一个递归方法遍历文件
	public static void printFile(File file) {
		//
		if(file.isDirectory()) {
			System.out.println(file.getPath());
		}
		// 1\出口 这是一个文件
		if (file.isFile()) {
			System.out.println(file.getName());
			return;
		}
		// 2\迭代的规律 目录
		// 获取文件列表
		File[] files = file.listFiles();
		// 遍历---printFile(File file)
		for (File file2 : files) {
			printFile(file2);
		}
	}
 
        

6.文件过滤器操作(文件过滤,文件名过滤)

import java.io.File;
import java.io.FilenameFilter;

//定义一个类来实现文件名过滤器和定义过滤规则,实现filenameFilter接口
public class FileFilterDemo implements FilenameFilter{

	//File pathname 是调用者的文件对象   name是过滤器遍历到的文件名
	@Override
	public boolean accept(File pathname,String name) {
		// System.out.println("accept--"+pathname.getAbsolutePath());
		// System.out.println(name);
		// 提取文件名中包含、的文件
		if(name.contains("、")) {
			return true;
		}
		// 
		return false;
	}

}
//测试
import java.io.File;
public class FileFilterMain{
public static void main(String[] args}{
	File  file1=new File("D:\\kk\\022");
	String[] files=file1.list(new FileFilterDemo());
    System.out.println("符合要求的");
    for(String file:files){
    System.out.println(file);
}
} 

案例一

获取文件名长度小于6的,要求使用匿名内部类
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;

public class FileFilterMain {

	public static void main(String[] args) {
		//
		File file1 = new File("D:\\GZ2204\\0217");
		// 在使用list获取文件数组时,可以提供过滤器来筛选符合要求的文件
		// String[] files = file1.list(new FileFilterDemo());
		String[] files = file1.list(new FilenameFilter() {
			@Override
			public boolean accept(File dir, String name) {
				return name.length() < 6;
			}
		});
		System.out.println("符合要求的:");
		//
		for (String file : files) {
			System.out.println(file);
		}
	}
}

案例二

获取文件名长度大于6的,要求使用匿名内部类
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;

public class FileFilterMain {

	public static void main(String[] args) {
	 //listFiles 可以入参FileFilter或者FileNameFilter
		 File[] files = file1.listFiles(new FileFilter() {
		 @Override
		 public boolean accept(File pathname) {
		 return pathname.getName().length()>=6;
		 }
		 });

		 System.out.println("符合要求的:");
		 //
		 for (File file : files) {
		 System.out.println(file.getName());
		 }
}
		

IO流的理解

JAVA的IO流是实现数据输入/输出的基础,在Java中把不同的输入、输出源(键盘、文件、网络连接等) 抽象地表述为流,使用流来访问数据;

 

流的分类

按照流向

--输入流、InputStream\Reader
--输出流、OutputStream\Writer

按照类型

-- 字节流、数据单元为8位的字节
-- 字符流、数据单元为16位的字节

按照角色

-- 节点流、针对某个特定的IO设备、比如磁盘、网络(低级流).

 

--处理流、针对已存在的流进行封装,进行高级的操作(高级流)

 

字节流(字节操作,用于文件的复制)

字节输入流、InputStream

--API:
read()方法:读取一个字节的数据,到文件的 结尾就为-1,在读取的时候,read方法会阻塞线程
read(byte[] b)方法:读取多个字节的数据到字节数组中

 1、读取单个字节数据

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;

字符的输入流
public class FileInputDemo {

	public static void main(String[] args) throws IOException {
		// 从硬盘中服读取文件数据 \
		// InputStream in = new FileInputStream(new File("D:\\GZ2204\\0217\\test.txt"));
		InputStream in = new FileInputStream("D:\\GZ2204\\0217\\test.txt");
		int res = in.read();// 读取一个字节的数据 一个byte,当数据读取完之后返回是-1
		System.out.println("读取到数据:" + (char) res);

		// int res =0;
		// //当数据流没有读取完,则一直读取
		// while((res=in.read())!=-1) {
		// System.out.print((char)res);
		// }
		// 关闭资源
		in.close();
	}

}
}

2、数据存储在字节数组中】一次性读取多个字节

方法一:字节数组
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;

字符的输入流
public class FileInputDemo {

	public static void main(String[] args) throws IOException {
		// 从硬盘中服读取文件数据 \
		// InputStream in = new FileInputStream(new File("D:\\GZ2204\\0217\\test.txt"));
		InputStream in = new FileInputStream("D:\\GZ2204\\0217\\test.txt");
		int res = in.read();// 读取一个字节的数据 一个byte,当数据读取完之后返回是-1
		System.out.println("读取到数据:" + (char) res);

		// int res =0;
		// //当数据流没有读取完,则一直读取
		// while((res=in.read())!=-1) {
		// System.out.print((char)res);
		// }
		// 关闭资源
		in.close();
	}

}

方法二:偏移

 
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;

字符的输入流
public class FileInputDemo {

	public static void main(String[] args) throws IOException {

		InputStream in = new FileInputStream("D:\\GZ2204\\0217\\test.txt");
		byte[] buff = new byte[100];
		// 存放的数组 ,偏移量【数组存放的位置偏移量】,读取到的个数
		int res = in.read(buff, 2, 10);// 从流中读取多个个字节的数据存放到字节数组中,当数据读取完之后返回是-1
		System.out.println("读取个数:" + res);
		for (int i = 0; i < res; i++) {
		System.out.print((char) buff[i]);
		 }
		System.out.println();
		System.out.println(Arrays.toString(buff));
		System.out.println((char) (32));
		in.close();
	}

}

字节输出流、OutputStream

--API:
write(int i)方法:写出一个字节的数据;(true表示为追加模式,默认为false【覆盖】)
write(byte[] b)方法:写出多个字节的数
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

//字节流--File输出字节流
public class FileOutputDemo {

	public static void main(String[] args) throws IOException {
		// 创建输出流的对象
		// OutputStream out = new FileOutputStream(new.0
		// File("D:\\GZ2204\\0217\\test.txt"));
		OutputStream out = new FileOutputStream("D:\\GZ2204\\0217\\test.txt", true);
        
		// 1、写出一个字节的数据,默认是覆盖原来文件中的数据
		// out.write((int)'中');//如果超过byte的范围则无法正确编解码

		// 2、写出多个字节的数据,默认是覆盖原来文件中的数据
		out.write("多个字节".getBytes());//getBytes() 翻译成字节数组
        
		//系统中的换行:
			// Windows系统里,每行结尾是 回车+换行 ,即\r\n;
			// Unix系统里【linux】,每行结尾只有 换行 ,即\n;
			// Mac系统里,每行结尾是 回车 ,即\r。
		out.write("\r\n".getBytes());
		out.write("123".getBytes());
        
		// 3、写出多个字节的数据,默认是覆盖原来文件中的数据,有偏移量
		// out.write("abc".getBytes(), 1, 2);getBytes数组长度为3,若偏移1,再加上取3个数据则越界
        //1,2偏移量和个数

		// 关闭资源
		out.close();

	}

}

例子

使用字节流实现文件复制

操作一:一个个读取(时长比较长)

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

//使用字节流实现文件复制
public class FIieCopyDemo {

	public static void main(String[] args) throws IOException {
		// 文件复制:
		// 把D:\GZ2204\0217\img.png 复制到 D:\GZ2204\0217\demo\img.png
		// 1、 从来源读取数据【输入流】
		FileInputStream in = new FileInputStream("D:\\软件\\mysql-5.7.34-winx64.zip");
		// 2、把数据写出目标【输出流】
		FileOutputStream out = new FileOutputStream("D:\\GZ2204\\0217\\demo\\mysql-5.7.34-winx1.zip");
		// 3、复制:不断的读、不断的写【直到写完为止】
		int len = 0;
		while((len = in.read())!=-1) {//读取
			//写出
			out.write(len);
		}
		//关闭资源
		in.close();
		out.close();
		System.out.println("复制成功");
	}

}

操作二:多个读取(时长比较短,但要注意数组大小)

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

//使用字节流实现文件复制
public class FIieCopyDemo {

	public static void main(String[] args) throws IOException {
		// 文件复制:
		// 把D:\GZ2204\0217\img.png 复制到 D:\GZ2204\0217\demo\img.png
		// 1、 从来源读取数据【输入流】
		FileInputStream in = new FileInputStream("D:\\软件\\mysql-5.7.34-winx64.zip");
		// 2、把数据写出目标【输出流】
		FileOutputStream out = new FileOutputStream("D:\\GZ2204\\0217\\demo\\mysql-5.7.34-winx1.zip");
		//操作二:多个字节操作   
        //10000byte --  635ms     5000000byte       --468ms     500000000byte  ---  1316ms
	//long start = System.currentTimeMillis();//时间测试
		byte[] buff = new byte[500000000];//数组大小并不是越大越快,要适当大小才能达到最快
		int len = 0;
		while((len = in.read(buff))!=-1) {//读取len个字节
			//写出len个字节
			out.write(buff,0,len);
		}
	//	long end = System.currentTimeMillis();//时间测试
	//	System.out.println(end-start);//时间测试
        
		//关闭资源
		in.close();
		out.close();
		System.out.println("复制成功");
	}

}		
		

字符流(字符操作,用于文本)【字节流+编码表】

字符输入流、Reader

--API:
read(char[] c)方法:读取多个字符的数据到字符数组中
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

//字符流--操作与字节流类似,只是一个字符大小为两个字节;
public class FileReaderDemo {

	public static void main(String[] args) throws IOException {
		// 从硬盘中服读取文件数据 \
		// InputStream in = new FileInputStream(new File("D:\\GZ2204\\0217\\test.txt"));
		FileReader in = new FileReader("D:\\GZ2204\\0217\\test.txt");
		// 1、读取单个字符数据
		// int res = in.read();//读取一个字节的数据 一个字符,当数据读取完之后返回是-1
		// System.out.println("读取到数据:"+(char)res);

		// int res =0;
		// //当数据流没有读取完,则一直读取
		// while((res=in.read())!=-1) {
		// System.out.print((char)res);
		// }

		// 2、数据存储在字节数组中一次性读取多个字符
		char[] buff = new char[100];
		int res = in.read(buff);// 从流中读取多个字符的数据存放到字符数组中,当数据读取完之后返回是-1
		System.out.println(res);
		// 将字符数组中的数据展示出来
		for (int i = 0; i < res; i++) {
			System.out.print((char) buff[i]);
		}

		// 3、数据存储在字节数组中一次性读取多个字节,可以设定偏移量和个数

		// 关闭资源
		in.close();
	}

}
 
        

字符输出流、Writer

--API:
write(String str)方法:写出一个字符串数据
注意:输出流写出数据数据时,默认会覆盖之前的数据,可以使用构造器将append设置为true,即为追加模式
(字符流应用在与文本相关一些操作,字节流应用于文件复制等操作)
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

//字符的输出流   
public class FileWriteDemo {

	//字符:字节+编码 	
	public static void main(String[] args) throws IOException {
		//
		Writer out = new FileWriter("D:\\GZ2204\\0217\\test.txt");
		// 1、写出单个字节数据
		// out.write(int c);
		// out.write(char[] c);
		out.write("你好!admin111!");
		// 使用flush方法将内置缓冲区中的数据刷新,持久化
		// out.flush();//若数据量没达到阈值,不会写出可以用flush刷新
		out.close();//会默认调用flush(),然后在关闭资源
	}

}
 
        

缓冲流【字节缓冲流,字符缓冲流】

 

具备一个 内置的缓冲区,默认的大小为8192,所以比普通字节流的操作速率要高;
字符缓冲流【字符输入流有readLine方法、字符输出流有newLine方法】
API:
readLine方法//输入流BufferStream
newLine方法//输出流:BufferReader

字符输入缓冲流

BufferedInputStream 和InputStream的速度对比
//一个字节与多个字节是速度比较;像是搬砖,从A地到B地,拿车搬和一块块搬速度有很大差别;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class BufferStreamDemo {public static void main(String[] args) throws IOException {
	 //缓冲流--包装流--处理流
	 FileInputStream fin = new FileInputStream("D:\\GZ2204\\0217\\5、文件过滤器.wmv");
	 FileOutputStream fout = new FileOutputStream("D:\\GZ2204\\0218\\文件过滤器.wmv");
	 // 创建一个缓冲字节输入流 -- 提供一个字节流的对象
	 BufferedInputStream in = new BufferedInputStream(fin);
	 BufferedOutputStream out = new BufferedOutputStream(fout);
	 //
	 long start = System.currentTimeMillis();
	 //一个字节操作
	 int data = 0;
	 while((data=fin.read())!=-1) {
	 fout.write(data);
	 }
	 //
	 fin.close();
	 fout.close();
	 //
	 long end = System.currentTimeMillis();//BufferedInputStream 527 InputStream161103
	 System.out.println(end-start);
	 }
 }

字符输出缓冲流

 
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

//缓冲流:特点:缓冲区
public class BufferReaderDemo {

	public static void main(String[] args) throws IOException {
		// 使用字符缓冲流来读取数据
		BufferedReader reader = new BufferedReader(new FileReader("D:\\GZ2204\\0218\\test.txt"));
		//
		//int res = reader.read();
		//
		//System.out.println((char)res);
		
		//当reader读取完数据流之后readLine()则返回null,test.txt共有4行数据
		System.out.println(reader.readLine());
		System.out.println(reader.readLine());
		System.out.println(reader.readLine());
		System.out.println(reader.readLine());
		System.out.println(reader.readLine());		
		//字符输出缓冲流  [注意:写出数据时,要刷新数据]
		 BufferedWriter writer = new BufferedWriter(new
		 FileWriter("D:\\GZ2204\\0218\\test.txt", true));//true为追加数据
	
		 writer.write("a");
		// writer.close();//close之后就添加不了数据了
		 writer.newLine();//创建新的一行【换行】
		 writer.write("b");
		 writer.close();
		 //把在缓冲区的数据写出到流中
		// writer.flush();
		// writer.close();
	}

}

练习

1.把test.txt中的所有信息进行遍历打印
 
public class BufferReaderDemo {

	public static void main(String[] args) throws IOException {
		// 使用字符缓冲流来读取数据
		BufferedReader reader = new BufferedReader(new FileReader("D:\\GZ2204\\0218\\test.txt"));
		String input = "";
		 while((input=reader.readLine())!=null) {
		 System.out.println(input);
		 }
    }
2.现在要拼接一个长度比较大的字符串--用StringBuilder, 因为经常用字符替换操
 	public class BufferReaderDemo {

	public static void main(String[] args) throws IOException {
        BufferedWriter writer = new BufferedWriter(new
		 FileWriter("D:\\GZ2204\\0218\\test.txt", true));//true为追加数据		
		StringBuilder sb = new StringBuilder("a");
		 for(int i=1;i<=8192;i++) {
		 sb.append("1234");
		 }
		 //
		writer.write(sb.toString());
    }
3.文本排序:请将文本信息恢复顺序。
文档1排序好--》文本档2
3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉以咨之,然后施行,必得裨补阙漏,有所广益。
8.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜自谋,以咨诹善道,察纳雅言,深追先帝遗诏,臣不胜受恩感激。
4.将军向宠,性行淑均,晓畅军事,试用之于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。
2.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内外异法也。
1.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。
9.今当远离,临表涕零,不知所言。
6.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。
7.先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐付托不效,以伤先帝之明,故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。
5.亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;


public class Demo {

	public static void main(String[] args) throws IOException {
		// 文档1排序好--》文本档2
		//1、使用字符缓冲流读取文档1
		BufferedReader reader = new BufferedReader(new FileReader("D:\\GZ2204\\0218\\data1.txt"));
		//2、排序
			//定义一个集合用于存放数据【字符串】
		List<String> list = new ArrayList<>();
			//把一行一行数据都读取
		String input = "";
		while((input=reader.readLine())!=null) {
			//保存到list
			list.add(input);
		}
			//排序
		Collections.sort(list, new Comparator<String>() {
			//根据第一个字符来进行排序
			@Override
			public int compare(String o1, String o2) {
				return o1.substring(0, 1).compareTo(o2.substring(0, 1));
			}
		});
		//3、使用字符缓冲流写出文档2
		BufferedWriter writer = new BufferedWriter(new FileWriter("D:\\GZ2204\\0218\\data2.txt"));
		for (String string : list) {
			writer.write(string);
			writer.newLine();//换行操作
			System.out.println(string);
		}
		//4、关闭资源
		reader.close();
		writer.close();
	}

}

字符集

 

字符集定义

也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。
  • ASCII字符集 :
  • ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)。
  • 基本的ASCII字符集,使用7位(bits)表示一个字符,共128字符。ASCII的扩展字符集使用8位(bits)表示一个字符,共256字符,方便支持欧洲常用字符。
  • ISO-8859-1字符集:
  • 拉丁码表,别名Latin-1,用于显示欧洲使用的语言,包括荷兰、丹麦、德语、意大利语、西班牙语等。
  • ISO-8859-1使用单字节编码,兼容ASCII编码。
  • GBxxx字符集:
  • GB就是国标的意思,是为了显示中文而设计的一套字符集。
  • GB2312:简体中文码表。一个小于127的字符的意义与原来相同。但两个大于127的字符连在一起时,就表示一个汉字,这样大约可以组合了包含7000多个简体汉字,此外数学符号、罗马希腊的字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。
  • GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等。
  • GB18030:最新的中文码表。收录汉字70244个,采用多字节编码,每个字可以由1个、2个或4个字节组成。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等。
  • Unicode字符集 :
  • Unicode编码系统为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国码。
  • 它最多使用4个字节的数字来表达每个字母、符号,或者文字。有三种编码方案,UTF-8、UTF-16和UTF-32。最为常用的UTF-8编码。
  • UTF-8编码,可以用来表示Unicode标准中任何字符,它是电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。所以,我们开发Web应用,也要使用UTF-8编码。它使用一至四个字节为每个字符编码,编码规则:
    • 128个US-ASCII字符,只需一个字节编码。
    • 拉丁文等字符,需要二个字节编码。
    • 大部分常用字(含中文),使用三个字节编码。
    • 其他极少使用的Unicode辅助字符,使用四字节编码。

处理流(包装流)

在已有的流的基础上进行包装,实现高级操作【可以设定字符集编码】

转换流

包括InputStreamReader、OutputStreamWriter--在构建转换流时, 可以封装节点流并指定编码集
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

//使用转换流读取数出数据     可以指定字符集编码
public class TransformDemo {

public static void main(String[] args) throws IOException, FileNotFoundException {
		// Reader  转换流--包装流
		InputStreamReader in = new InputStreamReader(new FileInputStream("D:\\GZ2204\\0218\\data2.txt"), "GBK");
		//
		int len = 0;
		//
		char[] buff = new char[1024];
		//
		while((len=in.read(buff))!=-1) {
			System.out.println(new String(buff,0,len));
		}
		in.close();
	}
}
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

//使用转换流写出数据   可以指定字符集编码
public class TransformDemo {
    public static void main(String[] args) throws IOException, FileNotFoundException {
		// Reader  转换流--包装流  可以指定字符集编码
		OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream("D:\\GZ2204\\0218\\data3.txt"), "UTF-8");
		//
		out.write("转换流--包装流");
		//
		out.close();
	}
	}

例子

1.把一个GBK编码的文档数据   转换到一个UTF-8

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

public class TransformDemo {
	public static void main(String[] args) throws IOException, FileNotFoundException {
	// Reader  转换流--包装流
	InputStreamReader in = new InputStreamReader(new FileInputStream("D:\\GZ2204\\0218\\data1.txt"), "GBK");
	// Reader  转换流--包装流
	OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream("D:\\GZ2204\\0218\\data2.txt"), "UTF-8");
	//
	int len = 0;
	//
	char[] buff = new char[1024];
	//
	while((len=in.read(buff))!=-1) {
		String data = new String(buff,0,len);
		System.out.println(data);
		out.write(data);//写出
	}
	//
	out.close();
	in.close();
}

}
2.键盘输入数据,在控制台中输出, 当键盘中输入exit时退出
public static void test() throws IOException{
        //包装流-转换流将系统输入字节流封装到转换流中System.in
        InputStreamReader reader=new InputStreamReader(System.in);
        //数据读取操作
        char[] c=new char[1024];
        int len;
        while((len=reader.read(c))!=-1){
        String input=new String(c,0,len);
        System.out.println(input);
        if(input.equals("exit\r\n"){
        break;
}}}

打印流 (PrintStream,PrintWriter)

1.输出语句中的out对象就是PrintStream
2.可以使用构造器给打印流设置字符集编码
3.PrintStream 设置编码的构造器
PrintStream ps = new PrintStream(file, csn);
PrintStream ps = new PrintStream(fileName, csn);
PrintStream ps = new PrintStream(out, autoFlush, encoding);
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;

//打印流,只有输出流,没有输入流  System.out.println()中的out是PrintStream的对象
public class PrintDemo {

	 public static void main(String[] args) throws IOException,
	 FileNotFoundException {
	 //
	 PrintStream printStream = new PrintStream(new
	 FileOutputStream("D:\\GZ2204\\0218\\data3.txt"), true, "GBK");
	 //
	 printStream.print("你好");
	 printStream.println("不好");
	 printStream.println("吧");
	 printStream.printf("我的名字是%s,性别是%s", "小王","男");
	 //
	 printStream.close();
	 }
}
4.PrintWriter设置编码的构造器
PrintWriter pw = new PrintWriter(file, csn);
PrintWriter pw = new PrintWriter(fileName, csn);
 
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
public class PrintDemo {
	public static void main(String[] args) throws IOException, FileNotFoundException {
		//
		PrintWriter printWriter = new PrintWriter("D:\\GZ2204\\0218\\data3.txt", "GBK");
		//
		printWriter.print("你好!");
		printWriter.println("不好");
		printWriter.println("吧!");
		printWriter.printf("我的名字是%s,性别是%s", "小王", "男");
		//使用PrintWriter,要注意flush
		printWriter.flush();
		//
		printWriter.close();
	}

}
5.API:
print()方法:各种类型数据的打印方法,没有换行
printf()方法:安照字符串格式进行打印输出的方法
println()方法:各种类型数据的打印方法,有换行的

RandomAccessFile(随机访问类,针对文件)

"r":以只读方式打开指定文件。如果试图对该RandomAccessFile执行写入方法,都将抛出IOException异常。
"rw":以读、写方式打开指定文件。如果该文件尚不存在,则尝试创建该文件。
"rws":以读、写方式打开指定文件。相对于"rw"模式,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
"rwd":以读、写方式打开指定文件。相对于"rw"模式,还要求对文件内容的每个更新都同步写入到底层存储设备。

API:

readLine():读取一行
seek():设置读取或写出数据操作的偏移量
skipBytes():跳过多少个字节
注意:readUTF方法和writeUTF方法的编码和标准的UTF-8的编码是不一样的,所以在进行文本操作的时候要注意编码和解码一致

写出操作

public class RandomAccessFileDemo {

	public static void main(String[] args) throws IOException {
		// 创建RandomAccessFile
		RandomAccessFile ranfile = new RandomAccessFile("D:\\GZ2204\\0221\\test.txt", "rw");
		//写出操作
		System.out.println(ranfile.length());
		// seek方法设置设定下次读取或者写出的位置
	    ranfile.seek(ranfile.length());
		ranfile.write("-abc".getBytes());			
		ranfile.close();
	}
}

读取操作

 
public class RandomAccessFileDemo {

	public static void main(String[] args) throws IOException {
		// 创建RandomAccessFile
		RandomAccessFile ranfile = new RandomAccessFile("D:\\GZ2204\\0221\\test.txt", "rw");=
		//		String line = ranfile.readLine();//读取一行
        //		ranfile.skipBytes(1);//跳过多少个字节(跳过1个字节)
		char line = (char) ranfile.readByte();//读取单个
        System.out.println(line);
		ranfile.close();
	}

}

特殊操作

 
import java.io.IOException;
import java.io.RandomAccessFile;
public class RandomAccessFileDemo {

	public static void main(String[] args) throws IOException {
		// 创建RandomAccessFile
		RandomAccessFile ranfile = new RandomAccessFile("D:\\GZ2204\\0221\\test.txt", "rw");
		 
		//特殊操作  针对utf的字符进行操作,建议使用本身读写方法
//		ranfile.writeUTF("电饭锅电饭锅");
		String line = ranfile.readUTF();
		System.out.println(line);
		
		ranfile.close();
	}

}

对象流(对象序列化)

定义

序列化(将对象序列化为字节序列,可以保存在磁盘中或者通过网络进行传输)、反序列化(字节序列解析为对象)例子:可以应用于网络传输、比如写qq聊天室

API:

writeObject();
readObject();
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class ObjectStreamTest {

	public static void main(String[] args) throws IOException, ClassNotFoundException {
//		// 对象输出流  --  序列化操作
//		ObjectOutputStream obs = new ObjectOutputStream(new FileOutputStream("D:\\GZ2204\\0221\\test.txt"));
//		//创建一个对象
//		Person p = new Person(1, "zhang", "male", 35);
//		//写出    //NotSerializableException  
//		obs.writeObject(p);
		
		
		// 对象输入流  --  反序列化操作
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\GZ2204\\0221\\test.txt"));
		//
		Object obj = ois.readObject();
		//
		System.out.println(obj);
	}

}

注意

1.读写的 对象类型必须要实现可序列化的接口Serializable
2.当类中有 某一个类的引用时,则该引用类型也要实现 可序列化接口
3. 同一个对象 只序列化一次,所以当对象数据修改后,再输出也是同样的数据(序列号相同,则不再序列化)
4.在 实现序列化时,建议生成序列化版本号, 用于辨识当前类,即使类被修改之后,也能将字节序列反序列 化为正确类型的对象。(即在写类时,加上版本号,以便于在修改后也能识别当前类)
import java.io.Serializable;

//人类
public class Person implements Serializable {

	/**
	 * 序列化的版本id 是在类进行升级改造之后,可以通过id去反序列化数据回来,解决兼容性问题
	 * 比如:  Person [id、name、sex、age]  把对象数据存储在文档中
	 * 后面升级改造  Person [id、name、sex、age、address]
	 * 由于版本id的存在,即使类修改结构之后,都会认定为同一个版本,则可以进行反序列化
	 */
	private static final long serialVersionUID = 1L;

	private int id;
	private String name;
	private String sex;
	private int age;

	private String address;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getSex() {
		return sex;
	}

	public void setSex(String sex) {
		this.sex = sex;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	public Person(int id, String name, String sex, int age, String address) {
		super();
		this.id = id;
		this.name = name;
		this.sex = sex;
		this.age = age;
		this.address = address;
	}

	public Person() {
		super();
	}

	//
	@Override
	public String toString() {
		return "Person [id=" + id + ", name=" + name + ", sex=" + sex + ", age=" + age + ", address=" + address + "]";
	}

	public Person(int id, String name, String sex, int age) {
		super();
		this.id = id;
		this.name = name;
		this.sex = sex;
		this.age = age;
	}

}
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class ObjectStreamTest2 {

	public static void main(String[] args) throws IOException, ClassNotFoundException {
//		// 对象输出流  --  序列化操作
		ObjectOutputStream obs = new ObjectOutputStream(new FileOutputStream("D:\\GZ2204\\0221\\test.txt"));
		//创建一个对象
		Person p = new Person(1, "li", "fmale", 35,"haizhu");
		//修改了对象数据
		//写出    //NotSerializableException  
		obs.writeObject(p);
		//
		obs.close();
		
//		 对象输入流  --  反序列化操作
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\GZ2204\\0221\\test.txt"));
		//1
		Object obj = ois.readObject();
		//
		System.out.println(obj);
	}

}

Properties属性类

一般结合io流,读取配置文件来配置环境

常用操作

--properties.setProperty("name", "张三");//设置
--properties.getProperty("name")//获取
	public static void main(String[] args) throws FileNotFoundException, IOException {
//		// Properties   	//类似map集合操作
		Properties properties = new Properties();
		//set、get
		properties.setProperty("name", "张三");
		properties.setProperty("sex", "男");
		properties.setProperty("age", "15");
		//
		System.out.println(properties);
       //获取操作
		System.out.println(properties.getProperty("name"));
		
		//遍历properties
		Set<String> names = properties.stringPropertyNames();
		for (String string : names) {
			System.out.println(string+"=="+properties.getProperty(string));
		}
 
        
--properties.load(new FileInputStream("D:\\GZ2204\\0221\\system.txt"));
--获取key集合properties.stringPropertyNames();
 
public class PropertiesDemo {
	public static void main(String[] args) throws FileNotFoundException, IOException {
		
		//在框架中会看见
		Properties properties = new Properties();
		//加载文件
		properties.load(new FileInputStream("D:\\GZ2204\\0221\\system.properties"));
		//遍历properties
		Set<String> names = properties.stringPropertyNames();
		for (String string : names) {
			System.out.println(string+"=="+properties.getProperty(string));
		}
		
		
	}

}
 
         

 

IO流总结

字节流[字节]
lnputStream --> FilelnputStream[单个字节、多个字节、偏移量]OutputStream --> FileOutputStream
字符流[字符]==》字节+编码{GBK两个字节UTF-8两个或者三字节}
Reader-->FileReader[单个字符、多个字符、偏移量]
Writer--> FileWriter
缓冲流:[缓冲区]【默认缓冲区长度8192】【io交互次数少,操作性能高】【封装了低级流】
BufferedInputStream
BufferedOutputStream
BufferedReader
BufferedWriter[注意: flush]特有方法
readLine();newLine();
文件复制:BufferedInputStream和BufferedOutputStream再外加一个存储数组
文本操作:BufferedReader和BufferedWriter

NIO的理解

java1.4后提出的新的io流

 

1.传统的IO流会进行阻塞操作、一个字节一个字节操作
2.NIO提供了一个区域块的 数据映射异步的IO流操作
3.通道是 用于获取流中数据
4.缓冲区是 用于保存通道中的数据

IO和NIO的区别

IO:1.面向流(Stream) 2.阻塞IO(Blocking IO)
NIO:1.面向缓冲区 2.非阻塞IO(NO Blocking IO)3. 选择器(Selectors)

Buffer(缓冲区)

 

定义

缓冲区中的四个核心属性:
capacity:容量,表示缓冲区中最大存储数据的容量。一旦声明不能改变。
limit:界限,表示缓冲区中可以操作数据的大小。(limit后数据不能进行读写)  
position:位置,表示缓冲区中正在操作数据的位置。
mark:标记,表示记录当前position位置。可以通过reset()恢复到mark的位置。

API

allocate();//创建
put();//添加
flip();//翻转
get();//读取
clear();//清除
根据数据类型的不同(boolean 除外),有以下 Buffer 常用子类: * ByteBuffer * CharBuffer * ShortBuffer * IntBuffer * LongBuffer * FloatBuffer * DoubleBuffer * 上述缓冲区的 管理方式几乎一致 通过allocate()获取缓冲区
 
import java.nio.ByteBuffer;

public class BufferDemo {

	public static void main(String[] args) {
		// 创建一个缓冲区[字节]   
		ByteBuffer buffer = ByteBuffer.allocate(100);//allocate创建,初始化长度
		// 属性信息
		System.out.println(buffer.position());//定位
		System.out.println(buffer.limit());//限制
		System.out.println(buffer.capacity());//容量
		//添加数据
		buffer.put((byte)97);
		buffer.put((byte)98);
		buffer.put((byte)99);
		//
		System.out.println(buffer.position());//定位
		System.out.println(buffer.limit());//限制
		System.out.println(buffer.capacity());//容量
		//准备读取操作
		//翻转
		buffer.flip();
		//
		System.out.println(buffer.position());//定位
		System.out.println(buffer.limit());//限制
		System.out.println(buffer.capacity());//容量
		//读取--(翻转之后再读取)
		System.out.println(buffer.get());
		//
		System.out.println(buffer.position());//定位
		System.out.println(buffer.limit());//限制
		System.out.println(buffer.capacity());//容量
		//清除     把位置都还原,然后方便下一次新的操作,注意数据是不会删除的
		buffer.clear();
		System.out.println(buffer.position());//定位
		System.out.println(buffer.limit());//限制		
	}
}

案例

 

1、读写 写一次、读一次 写入15个字符 读取6个 和读写 写一次、读一次 写入20个字符 读取20个【新的操作】
import java.nio.CharBuffer;
public class BufferDemo2 {
	public static void main(String[] args) {
		// 1、读写 写一次、读一次 写入15个字符 读取6个
		// 创建一个缓冲区[字节]
		CharBuffer buffer = CharBuffer.allocate(100);// allocate创建,初始化长度
		// 2、读写 写一次、读一次 写入20个字符 读取20个【新的操作】
		buffer.put("it is a demo hey");
		buffer.flip();
//		char[] demo = new char[6];
		//
		for(int i=1;i<=6;i++) {
			System.out.print(buffer.get());
		}
		
		//清空
		buffer.clear();
		
		System.out.println(buffer.position());//定位
		System.out.println(buffer.limit());//限制
		System.out.println(buffer.capacity());//容量
		
		
		//添加
		buffer.put("it is a demo hey hello");
		
		//翻转
		buffer.flip();
		System.out.println(buffer.position());//定位
		System.out.println(buffer.limit());//限制
		System.out.println(buffer.capacity());//容量
		//新操作
		for(int i=1;i<=20;i++) {
			System.out.print(buffer.get());
		}
		
	}

}
 
        
2.重复读6个数据; --mark和reset 的使用
import java.nio.CharBuffer;

public class BufferDemo3 {

	public static void main(String[] args) {
		// 创建一个缓冲区[字节]
		CharBuffer buffer = CharBuffer.allocate(100);// allocate创建,初始化长度
		// 2、读写 写一次、读一次 写入20个字符 读取20个【新的操作】
		buffer.put("it is a demo hey");
		buffer.flip();
		
		//读一个
		buffer.get();
		//标记
		buffer.mark();//已经读了一个,标记即为1;
		System.out.println(buffer.position());//定位
		
		//读取
		for(int i=1;i<=6;i++) {
			System.out.print(buffer.get());
		}
		System.out.println();
		System.out.println(buffer.position());//定位
		
		//重置位置到 mark的位置
		buffer.reset();
		System.out.println(buffer.position());//定位
		for(int i=1;i<=6;i++) {
			System.out.print(buffer.get());
		}
		System.out.println();
		System.out.println(buffer.position());//定位
		
	}

}

Channel

Channel接口的主要实现类
  • FileChannel 本地文件传输通道
  • SocketChannel/ServerSocketChannel TCP协议数据传输通道
  • DatagramChannel UDP协议传输通道
  • 本地IO:FileInputStream、FileOutputStream、RandomAccessFile
--通道,通过流的getChannel方法来获取通道的对象

API:

将通道中的数据映射到buff中,其中第一个参数为操作模式,第二个参数为操作的开始位置,第三个参数为映射的个数
map(FileChannel.MapMode.READ_ONLY, 0, file.length());

写数据

 
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Arrays;

public class ChnnelBufferDemo {

	public static void main(String[] args) throws IOException {
		// 创建一个缓冲区[字节]
		ByteBuffer buffer = ByteBuffer.allocate(100);// allocate创建,初始化长度

		buffer.put("it is a demo hey".getBytes());
		//翻转  将position设置为0 --写出前要翻转,这样才能将buffer中数据写出来
		buffer.flip();
		// 创建文件io流
		FileOutputStream out = new FileOutputStream("D:\\GZ2204\\0221\\test.txt");
//		// 获取通道
		FileChannel channel = out.getChannel();//获取通道的对象
		// 通过通道将buffer中的数据写出
		channel.write(buffer);
		channel.close();
		out.close();		
	}

}

读数据

import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Arrays;

public class ChnnelBufferDemo {
	public static void main(String[] args) throws IOException {
		//练习 : 将现在test.txt的数据使用NIO读取出来
		// 创建一个缓冲区[字节]
		ByteBuffer buffer = ByteBuffer.allocate(100);// allocate创建,初始化长度
		// 创建文件io流
		FileInputStream in = new FileInputStream("D:\\GZ2204\\0221\\test.txt");
		// 获取通道
		FileChannel channel = in.getChannel();
		//先把数据读取到buffer中
		int len = channel.read(buffer);
		//提取字节数据
		byte[] dst = Arrays.copyOf(buffer.array(), len);
		//string转换
		System.out.println(new String(dst));	
	}
}

练习

1.将现在test.txt的数据使用NIO写出到data.txt中   test.txt中有大量的字符数据

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class HomeWork {

	public static void main(String[] args) throws IOException {
		//缓冲区
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		//通道
		FileInputStream in = new FileInputStream("D:\\GZ2204\\0221\\test.txt");
		FileOutputStream out = new FileOutputStream("D:\\GZ2204\\0221\\data.txt");
		FileChannel  inc = in.getChannel();
		FileChannel  outc = out.getChannel();
		//
		int len = 0;
		while((len=inc.read(buffer))!=-1) {//读取
			//翻转
			buffer.flip();
			//写出
			outc.write(buffer);
			//清除
			buffer.clear();
		}
		//关闭资源
		inc.close();
		outc.close();
		in.close();
		out.close();
	}

	
}
2.
查找文件内容 【技术不限-使用io流即可】
定义一个方法规定入参为文件目录以及查找的字符串
search(String path,String str)
效果:在d:\xxx\xxx.txt中找到了xxx字符串
 
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class HomeWork2 {

	public static void main(String[] args) throws IOException {
		search("D:\\GZ2204\\0221", "demo");
	}

	// 迭代 --递归方法
	public static void search(String path, String content) throws IOException {
		// 创建文件对象
		File file = new File(path);
		// System.out.println("--"+file.getAbsolutePath());
		// 出口--文件
		if (file.isFile()) {
			// 判断文件类型 .txt .properties
			String type = path.substring(path.lastIndexOf("."));
			
			if (type.equals(".txt") || type.equals(".properties")) {
				// 读取数据判断
				// --io流读取
				BufferedReader reader = new BufferedReader(new FileReader(file));
				// 判断
				String line = "";
				while ((line = reader.readLine()) != null) {
					// 是否有包含字符串
					if (line.contains(content)) {
						System.out.println(file.getAbsolutePath());
					}
				}
			}
		} else {// --文件夹
				// 迭代的规律
				// 获取子文件列表
			File[] files = file.listFiles();
			// 遍历递归调用
			for (File file2 : files) {
				search(file2.getAbsolutePath(), content);
			}
		}
	}
}
 
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值