`
myCsdn_taoge
  • 浏览: 40560 次
  • 性别: Icon_minigender_1
  • 来自: 沈阳
文章分类
社区版块
存档分类
最新评论

Java IO 整理

阅读更多

写在前面:本文章基本覆盖了java IO的全部内容,java新IO没有涉及,因为我想和这个分开,以突出那个的重要性,新IO哪一篇文章还没有开始写,估计很快就能和大家见面。照旧,文章依旧以例子为主,因为讲解内容的java书很多了,我觉的学以致用才是真。代码是写出来的,不是看出来的。

最后欢迎大家提出意见和建议。

【案例1】创建一个新文件

1
2
3
4
5
6
7
8
9
10
11
import java.io.*;
class hello{
 public static void main(String[] args) {
 File f=new File("D:\\hello.txt");
 try{
 f.createNewFile();
 }catch (Exception e) {
 e.printStackTrace();
 }
 }
}

【运行结果】:

程序运行之后,在d盘下会有一个名字为hello.txt的文件。

【案例2】File类的两个常量

1
2
3
4
5
6
7
import java.io.*;
class hello{
 public static void main(String[] args) {
 System.out.println(File.separator);
 System.out.println(File.pathSeparator);
 }
}

【运行结果】:

\

;

此处多说几句:有些同学可能认为,我直接在windows下使用\进行分割不行吗?当然是可以的。但是在linux下就不是\了。所以,要想使得我们的代码跨平台,更加健壮,所以,大家都采用这两个常量吧,其实也多写不了几行。呵呵、
现在我们使用File类中的常量改写上面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
import java.io.*;
class hello{
 public static void main(String[] args) {
 String fileName="D:"+File.separator+"hello.txt";
 File f=new File(fileName);
 try{
 f.createNewFile();
 }catch (Exception e) {
 e.printStackTrace();
 }
 }
}

你看,没有多写多少吧,呵呵。所以建议使用File类中的常量。

删除一个文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * 删除一个文件
 * */
import java.io.*;
class hello{
 public static void main(String[] args) {
 String fileName="D:"+File.separator+"hello.txt";
 File f=new File(fileName);
 if(f.exists()){
 f.delete();
 }else{
 System.out.println("文件不存在");
 }
 
 }
}

创建一个文件夹

1
2
3
4
5
6
7
8
9
10
11
/**
 * 创建一个文件夹
 * */
import java.io.*;
class hello{
 public static void main(String[] args) {
 String fileName="D:"+File.separator+"hello";
 File f=new File(fileName);
 f.mkdir();
 }
}

【运行结果】:

D盘下多了一个hello文件夹

列出指定目录的全部文件(包括隐藏文件):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * 使用list列出指定目录的全部文件
 * */
import java.io.*;
class hello{
 public static void main(String[] args) {
 String fileName="D:"+File.separator;
 File f=new File(fileName);
 String[] str=f.list();
 for (int i = 0; i < str.length; i++) {
 System.out.println(str[i]);
 }
 }
}

【运行结果】:

$RECYCLE.BIN

360

360Downloads

360Rec

360SoftMove

Config.Msi

da

Downloads

DriversBackup

Eclipse

java web整合开发和项目实战

Lenovo

MSOCache

Program

Program Files

python

RECYGLER.{8F92DA15-A229-A4D5-B5CE-5280C8B89C19}

System Volume Information

Tomcat6

var

vod_cache_data

新建文件夹

(你的运行结果应该和这个不一样的,呵呵)

但是使用list返回的是String数组,。而且列出的不是完整路径,如果想列出完整路径的话,需要使用listFiles.他返回的是File的数组

列出指定目录的全部文件(包括隐藏文件):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 * 使用listFiles列出指定目录的全部文件
 * listFiles输出的是完整路径
 * */
import java.io.*;
class hello{
 public static void main(String[] args) {
 String fileName="D:"+File.separator;
 File f=new File(fileName);
 File[] str=f.listFiles();
 for (int i = 0; i < str.length; i++) {
 System.out.println(str[i]);
 }
 }
}

【运行结果】:

D:\$RECYCLE.BIN

D:\360

D:\360Downloads

D:\360Rec

D:\360SoftMove

D:\Config.Msi

D:\da

D:\Downloads

D:\DriversBackup

D:\eclipse

D:\java web整合开发和项目实战

D:\Lenovo

D:\MSOCache

D:\Program

D:\Program Files

D:\python

D:\RECYGLER.{8F92DA15-A229-A4D5-B5CE-5280C8B89C19}

D:\System Volume Information

D:\Tomcat6

D:\var

D:\vod_cache_data

D:\新建文件夹

通过比较可以指定,使用listFiles更加方便、

判断一个指定的路径是否为目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 * 使用isDirectory判断一个指定的路径是否为目录
 * */
import java.io.*;
class hello{
 public static void main(String[] args) {
 String fileName="D:"+File.separator;
 File f=new File(fileName);
 if(f.isDirectory()){
 System.out.println("YES");
 }else{
 System.out.println("NO");
 }
 }
}

【运行结果】:YES

搜索指定目录的全部内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
 * 列出指定目录的全部内容
 * */
import java.io.*;
class hello{
 public static void main(String[] args) {
 String fileName="D:"+File.separator;
 File f=new File(fileName);
 print(f);
 }
 public static void print(File f){
 if(f!=null){
 if(f.isDirectory()){
 File[] fileArray=f.listFiles();
 if(fileArray!=null){
 for (int i = 0; i < fileArray.length; i++) {
 //递归调用
 print(fileArray[i]);
 }
 }
 }
 else{
 System.out.println(f);
 }
 }
 }
}

【运行结果】:

D:\Tomcat6\work\Catalina\localhost\nevel\org\apache\jsp\framepages\web4welcome_jsp.java

D:\Tomcat6\work\Catalina\localhost\nevel\org\apache\jsp\help_005fhome_jsp.class

D:\Tomcat6\work\Catalina\localhost\nevel\org\apache\jsp\help_005fhome_jsp.java

D:\Tomcat6\work\Catalina\localhost\nevel\org\apache\jsp\home_jsp.class

D:\Tomcat6\work\Catalina\localhost\nevel\org\apache\jsp\home_jsp.java

D:\Tomcat6\work\Catalina\localhost\nevel\org\apache\jsp\index_jsp.class

D:\Tomcat6\work\Catalina\localhost\nevel\org\apache\jsp\index_jsp.java

D:\Tomcat6\work\Catalina\localhost\nevel\org\apache\jsp\login_jsp.class

D:\Tomcat6\work\Catalina\localhost\nevel\org\apache\jsp\login_jsp.java

D:\Tomcat6\work\Catalina\localhost\nevel\org\apache\jsp\modify_005fuser_005finfo_jsp.class

D:\Tomcat6\work\Catalina\localhost\nevel\org\apache\jsp\modify_005fuser_005finfo_jsp.java

D:\Tomcat6\work\Catalina\localhost\nevel\org\apache\jsp\register_005fnotify_jsp.class

D:\Tomcat6\work\Catalina\localhost\nevel\org\apache\jsp\register_005fnotify_jsp.java

D:\Tomcat6\work\Catalina\localhost\nevel\org\apache\jsp\sign_005fup_jsp.class

D:\Tomcat6\work\Catalina\localhost\nevel\org\apache\jsp\sign_005fup_jsp.java

D:\Tomcat6\work\Catalina\localhost\nevel\org\apache\jsp\transit_jsp.class

……

【使用RandomAccessFile写入文件】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
 * 使用RandomAccessFile写入文件
 * */
import java.io.*;
class hello{
 public static void main(String[] args) throws IOException {
 String fileName="D:"+File.separator+"hello.txt";
 File f=new File(fileName);
 RandomAccessFile demo=new RandomAccessFile(f,"rw");
 demo.writeBytes("asdsad");
 demo.writeInt(12);
 demo.writeBoolean(true);
 demo.writeChar('A');
 demo.writeFloat(1.21f);
 demo.writeDouble(12.123);
 demo.close();
 }
}

如果你此时打开hello。txt查看的话,会发现那是乱码。

字节流

【向文件中写入字符串】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * 字节流
 * 向文件中写入字符串
 * */
import java.io.*;
class hello{
 public static void main(String[] args) throws IOException {
 String fileName="D:"+File.separator+"hello.txt";
 File f=new File(fileName);
 OutputStream out =new FileOutputStream(f);
 String str="你好";
 byte[] b=str.getBytes();
 out.write(b);
 out.close();
 }
}

查看hello.txt会看到“你好”

当然也可以一个字节一个字节的写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
 * 字节流
 * 向文件中一个字节一个字节的写入字符串
 * */
import java.io.*;
class hello{
 public static void main(String[] args) throws IOException {
 String fileName="D:"+File.separator+"hello.txt";
 File f=new File(fileName);
 OutputStream out =new FileOutputStream(f);
 String str="你好";
 byte[] b=str.getBytes();
 for (int i = 0; i < b.length; i++) {
 out.write(b[i]);
 }
 out.close();
 }
}

结果还是:“你好”

向文件中追加新内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
 * 字节流
 * 向文件中追加新内容:
 * */
import java.io.*;
class hello{
 public static void main(String[] args) throws IOException {
 String fileName="D:"+File.separator+"hello.txt";
 File f=new File(fileName);
 OutputStream out =new FileOutputStream(f,true);
 String str="Rollen";
 //String str="\r\nRollen"; 可以换行
 byte[] b=str.getBytes();
 for (int i = 0; i < b.length; i++) {
 out.write(b[i]);
 }
 out.close();
 }
}

【运行结果】:

你好Rollen

【读取文件内容】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * 字节流
 * 读文件内容
 * */
import java.io.*;
class hello{
 public static void main(String[] args) throws IOException {
 String fileName="D:"+File.separator+"hello.txt";
 File f=new File(fileName);
 InputStream in=new FileInputStream(f);
 byte[] b=new byte[1024];
 in.read(b);
 in.close();
 System.out.println(new String(b));
 }
}

【运行结果】

你好Rollen

Rollen_

但是这个例子读取出来会有大量的空格,我们可以利用in.read(b);的返回值来设计程序。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * 字节流
 * 读文件内容
 * */
import java.io.*;
class hello{
 public static void main(String[] args) throws IOException {
 String fileName="D:"+File.separator+"hello.txt";
 File f=new File(fileName);
 InputStream in=new FileInputStream(f);
 byte[] b=new byte[1024];
 int len=in.read(b);
 in.close();
 System.out.println("读入长度为:"+len);
 System.out.println(new String(b,0,len));
 }
}

【运行结果】:

读入长度为:18

你好Rollen

Rollen

读者观察上面的例子可以看出,我们预先申请了一个指定大小的空间,但是有时候这个空间可能太小,有时候可能太大,我们需要准确的大小,这样节省空间,那么我们可以这样干:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * 字节流
 * 读文件内容,节省空间
 * */
import java.io.*;
class hello{
 public static void main(String[] args) throws IOException {
 String fileName="D:"+File.separator+"hello.txt";
 File f=new File(fileName);
 InputStream in=new FileInputStream(f);
 byte[] b=new byte[(int)f.length()];
 in.read(b);
 System.out.println("文件长度为:"+f.length());
 in.close();
 System.out.println(new String(b));
 }
}

文件长度为:18

你好Rollen

Rollen

将上面的例子改为一个一个读:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
 * 字节流
 * 读文件内容,节省空间
 * */
import java.io.*;
class hello{
 public static void main(String[] args) throws IOException {
 String fileName="D:"+File.separator+"hello.txt";
 File f=new File(fileName);
 InputStream in=new FileInputStream(f);
 byte[] b=new byte[(int)f.length()];
 for (int i = 0; i < b.length; i++) {
 b[i]=(byte)in.read();
 }
 in.close();
 System.out.println(new String(b));
 }
}

输出的结果和上面的一样。

细心的读者可能会发现,上面的几个例子都是在知道文件的内容多大,然后才展开的,有时候我们不知道文件有多大,这种情况下,我们需要判断是否独到文件的末尾。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
 * 字节流
 *读文件
 * */
import java.io.*;
class hello{
 public static void main(String[] args) throws IOException {
 String fileName="D:"+File.separator+"hello.txt";
 File f=new File(fileName);
 InputStream in=new FileInputStream(f);
 byte[] b=new byte[1024];
 int count =0;
 int temp=0;
 while((temp=in.read())!=(-1)){
 b[count++]=(byte)temp;
 }
 in.close();
 System.out.println(new String(b));
 }
}

【运行结果】

你好Rollen

Rollen_

提醒一下,当独到文件末尾的时候会返回-1.正常情况下是不会返回-1的

字符流


【向文件中写入数据】

现在我们使用字符流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 * 字符流
 * 写入数据
 * */
import java.io.*;
class hello{
 public static void main(String[] args) throws IOException {
 String fileName="D:"+File.separator+"hello.txt";
 File f=new File(fileName);
 Writer out =new FileWriter(f);
 String str="hello";
 out.write(str);
 out.close();
 }
}

当你打开hello。txt的时候,会看到hello

其实这个例子上之前的例子没什么区别,只是你可以直接输入字符串,而不需要你将字符串转化为字节数组。

当你如果想问文件中追加内容的时候,可以使用将上面的声明out的哪一行换为:

Writer out =new FileWriter(f,true);

这样,当你运行程序的时候,会发现文件内容变为:

hellohello如果想在文件中换行的话,需要使用“\r\n”

比如将str变为String str=”\r\nhello”;

这样文件追加的str的内容就会换行了。

从文件中读内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * 字符流
 * 从文件中读出内容
 * */
import java.io.*;
class hello{
 public static void main(String[] args) throws IOException {
 String fileName="D:"+File.separator+"hello.txt";
 File f=new File(fileName);
 char[] ch=new char[100];
 Reader read=new FileReader(f);
 int count=read.read(ch);
 read.close();
 System.out.println("读入的长度为:"+count);
 System.out.println("内容为"+new String(ch,0,count));
 }
}

【运行结果】:

读入的长度为:17

内容为hellohello

hello

当然最好采用循环读取的方式,因为我们有时候不知道文件到底有多大。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
 * 字符流
 * 从文件中读出内容
 * */
import java.io.*;
class hello{
 public static void main(String[] args) throws IOException {
 String fileName="D:"+File.separator+"hello.txt";
 File f=new File(fileName);
 char[] ch=new char[100];
 Reader read=new FileReader(f);
 int temp=0;
 int count=0;
 while((temp=read.read())!=(-1)){
 ch[count++]=(char)temp;
 }
 read.close();
 System.out.println("内容为"+new String(ch,0,count));
 }
}

运行结果:

内容为hellohello

hello

关于字节流和字符流的区别

实际上字节流在操作的时候本身是不会用到缓冲区的,是文件本身的直接操作的,但是字符流在操作的 时候下后是会用到缓冲区的,是通过缓冲区来操作文件的。

读者可以试着将上面的字节流和字符流的程序的最后一行关闭文件的代码注释掉,然后运行程序看看。你就会发现使用字节流的话,文件中已经存在内容,但是使用字符流的时候,文件中还是没有内容的,这个时候就要刷新缓冲区。

使用字节流好还是字符流好呢?

答案是字节流。首先因为硬盘上的所有文件都是以字节的形式进行传输或者保存的,包括图片等内容。但是字符只是在内存中才会形成的,所以在开发中,字节流使用广泛。

文件的复制

其实DOS下就有一个文件复制功能,比如我们想把d盘下面的hello.txt文件复制到d盘下面的rollen.txt文件中,那么我们就可以使用下面的命令:

1
copy d:\hello.txt d:\rollen.txt

运行之后你会在d盘中看见hello.txt.,并且两个文件的内容是一样的,(这是屁话)

下面我们使用程序来复制文件吧。

基本思路还是从一个文件中读入内容,边读边写入另一个文件,就是这么简单。、

首先编写下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
 * 文件的复制
 * */
import java.io.*;
class hello{
 public static void main(String[] args) throws IOException {
 if(args.length!=2){
 System.out.println("命令行参数输入有误,请检查");
 System.exit(1);
 }
 File file1=new File(args[0]);
 File file2=new File(args[1]);
 
 if(!file1.exists()){
 System.out.println("被复制的文件不存在");
 System.exit(1);
 }
 InputStream input=new FileInputStream(file1);
 OutputStream output=new FileOutputStream(file2);
 if((input!=null)&&(output!=null)){
 int temp=0;
 while((temp=input.read())!=(-1)){
 output.write(temp);
 }
 }
 input.close();
 output.close();
 }
}

然后在命令行下面

1
2
javac hello.java
java hello d:\hello.txt d:\rollen.txt

现在你就会在d盘看到rollen。txt了,

OutputStreramWriter 和InputStreamReader类

整个IO类中除了字节流和字符流还包括字节和字符转换流。

OutputStreramWriter将输出的字符流转化为字节流

InputStreamReader将输入的字节流转换为字符流

但是不管如何操作,最后都是以字节的形式保存在文件中的。

将字节输出流转化为字符输出流

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
 * 将字节输出流转化为字符输出流
 * */
import java.io.*;
class hello{
 public static void main(String[] args) throws IOException {
 String fileName= "d:"+File.separator+"hello.txt";
 File file=new File(fileName);
 Writer out=new OutputStreamWriter(new FileOutputStream(file));
 out.write("hello");
 out.close();
 }
}

运行结果:文件中内容为:hello

将字节输入流变为字符输入流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 * 将字节输入流变为字符输入流
 * */
import java.io.*;
class hello{
 public static void main(String[] args) throws IOException {
 String fileName= "d:"+File.separator+"hello.txt";
 File file=new File(fileName);
 Reader read=new InputStreamReader(new FileInputStream(file));
 char[] b=new char[100];
 int len=read.read(b);
 System.out.println(new String(b,0,len));
 read.close();
 }
}

【运行结果】:hello

前面列举的输出输入都是以文件进行的,现在我们以内容为输出输入目的地,使用内存操作流

ByteArrayInputStream 主要将内容写入内容

ByteArrayOutputStream 主要将内容从内存输出

使用内存操作流将一个大写字母转化为小写字母

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
 * 使用内存操作流将一个大写字母转化为小写字母
 * */
import java.io.*;
class hello{
 public static void main(String[] args) throws IOException {
 String str="ROLLENHOLT";
 ByteArrayInputStream input=new ByteArrayInputStream(str.getBytes());
 ByteArrayOutputStream output=new ByteArrayOutputStream();
 int temp=0;
 while((temp=input.read())!=-1){
 char ch=(char)temp;
 output.write(Character.toLowerCase(ch));
 }
 String outStr=output.toString();
 input.close();
 output.close();
 System.out.println(outStr);
 }
}

【运行结果】:

rollenholt

内容操作流一般使用来生成一些临时信息采用的,这样可以避免删除的麻烦。

管道流

管道流主要可以进行两个线程之间的通信。

PipedOutputStream 管道输出流

PipedInputStream 管道输入流

验证管道流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/**
 * 验证管道流
 * */
import java.io.*;
 
/**
 * 消息发送类
 * */
class Send implements Runnable{
 private PipedOutputStream out=null;
 public Send() {
 out=new PipedOutputStream();
 }
 public PipedOutputStream getOut(){
 return this.out;
 }
 public void run(){
 String message="hello , Rollen";
 try{
 out.write(message.getBytes());
 }catch (Exception e) {
 e.printStackTrace();
 }try{
 out.close();
 }catch (Exception e) {
 e.printStackTrace();
 }
 }
}
 
/**
 * 接受消息类
 * */
class Recive implements Runnable{
 private PipedInputStream input=null;
 public Recive(){
 this.input=new PipedInputStream();
 }
 public PipedInputStream getInput(){
 return this.input;
 }
 public void run(){
 byte[] b=new byte[1000];
 int len=0;
 try{
 len=this.input.read(b);
 }catch (Exception e) {
 e.printStackTrace();
 }try{
 input.close();
 }catch (Exception e) {
 e.printStackTrace();
 }
 System.out.println("接受的内容为 "+(new String(b,0,len)));
 }
}
/**
 * 测试类
 * */
class hello{
 public static void main(String[] args) throws IOException {
 Send send=new Send();
 Recive recive=new Recive();
 try{
//管道连接
 send.getOut().connect(recive.getInput());
 }catch (Exception e) {
 e.printStackTrace();
 }
 new Thread(send).start();
 new Thread(recive).start();
 }
}

【运行结果】:

接受的内容为 hello , Rollen

打印流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * 使用PrintStream进行输出
 * */
import java.io.*;
 
class hello {
 public static void main(String[] args) throws IOException {
 PrintStream print = new PrintStream(new FileOutputStream(new File("d:"
 + File.separator + "hello.txt")));
 print.println(true);
 print.println("Rollen");
 print.close();
 }
}

【运行结果】:

true

Rollen

当然也可以格式化输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 * 使用PrintStream进行输出
 * 并进行格式化
 * */
import java.io.*;
class hello {
 public static void main(String[] args) throws IOException {
 PrintStream print = new PrintStream(new FileOutputStream(new File("d:"
 + File.separator + "hello.txt")));
 String name="Rollen";
 int age=20;
 print.printf("姓名:%s. 年龄:%d.",name,age);
 print.close();
 }
}

【运行结果】:

姓名:Rollen. 年龄:20.

使用OutputStream向屏幕上输出内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
 * 使用OutputStream向屏幕上输出内容
 * */
import java.io.*;
class hello {
 public static void main(String[] args) throws IOException {
 OutputStream out=System.out;
 try{
 out.write("hello".getBytes());
 }catch (Exception e) {
 e.printStackTrace();
 }
 try{
 out.close();
 }catch (Exception e) {
 e.printStackTrace();
 }
 }
}

【运行结果】:

hello

输入输出重定向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
 
/**
 * 为System.out.println()重定向输出
 * */
public class systemDemo{
 public static void main(String[] args){
 // 此刻直接输出到屏幕
 System.out.println("hello");
 File file = new File("d:" + File.separator + "hello.txt");
 try{
 System.setOut(new PrintStream(new FileOutputStream(file)));
 }catch(FileNotFoundException e){
 e.printStackTrace();
 }
 System.out.println("这些内容在文件中才能看到哦!");
 }
}

【运行结果】:

eclipse的控制台输出的是hello。然后当我们查看d盘下面的hello.txt文件的时候,会在里面看到:这些内容在文件中才能看到哦!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
 
/**
 * System.err重定向 这个例子也提示我们可以使用这种方法保存错误信息
 * */
public class systemErr{
 public static void main(String[] args){
 File file = new File("d:" + File.separator + "hello.txt");
 System.err.println("这些在控制台输出");
 try{
 System.setErr(new PrintStream(new FileOutputStream(file)));
 }catch(FileNotFoundException e){
 e.printStackTrace();
 }
 System.err.println("这些在文件中才能看到哦!");
 }
}

【运行结果】:

你会在eclipse的控制台看到红色的输出:“这些在控制台输出”,然后在d盘下面的hello.txt中会看到:这些在文件中才能看到哦!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
 
/**
 * System.in重定向
 * */
public class systemIn{
 public static void main(String[] args){
 File file = new File("d:" + File.separator + "hello.txt");
 if(!file.exists()){
 return;
 }else{
 try{
 System.setIn(new FileInputStream(file));
 }catch(FileNotFoundException e){
 e.printStackTrace();
 }
 byte[] bytes = new byte[1024];
 int len = 0;
 try{
 len = System.in.read(bytes);
 }catch(IOException e){
 e.printStackTrace();
 }
 System.out.println("读入的内容为:" + new String(bytes, 0, len));
 }
 }
}

【运行结果】:

前提是我的d盘下面的hello.txt中的内容是:“这些文件中的内容哦!”,然后运行程序,输出的结果为:读入的内容为:这些文件中的内容哦!

BufferedReader的小例子

注意: BufferedReader只能接受字符流的缓冲区,因为每一个中文需要占据两个字节,所以需要将System.in这个字节输入流变为字符输入流,采用:

BufferedReader buf = new BufferedReader(
new InputStreamReader(System.in));

下面给一个实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
 
/**
 * 使用缓冲区从键盘上读入内容
 * */
public class BufferedReaderDemo{
 public static void main(String[] args){
 BufferedReader buf = new BufferedReader(
 new InputStreamReader(System.in));
 String str = null;
 System.out.println("请输入内容");
 try{
 str = buf.readLine();
 }catch(IOException e){
 e.printStackTrace();
 }
 System.out.println("你输入的内容是:" + str);
 }
}

运行结果:

请输入内容

dasdas

你输入的内容是:dasdas

Scanner类

其实我们比较常用的是采用Scanner类来进行数据输入,下面来给一个Scanner的例子吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.Scanner;
 
/**
 * Scanner的小例子,从键盘读数据
 * */
public class ScannerDemo{
 public static void main(String[] args){
 Scanner sca = new Scanner(System.in);
 // 读一个整数
 int temp = sca.nextInt();
 System.out.println(temp);
 //读取浮点数
 float flo=sca.nextFloat();
 System.out.println(flo);
 //读取字符
 //...等等的,都是一些太基础的,就不师范了。
 }
}

其实Scanner可以接受任何的输入流

下面给一个使用Scanner类从文件中读出内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
 
/**
 * Scanner的小例子,从文件中读内容
 * */
public class ScannerDemo{
 public static void main(String[] args){
 
 File file = new File("d:" + File.separator + "hello.txt");
 Scanner sca = null;
 try{
 sca = new Scanner(file);
 }catch(FileNotFoundException e){
 e.printStackTrace();
 }
 String str = sca.next();
 System.out.println("从文件中读取的内容是:" + str);
 }
}

【运行结果】:

从文件中读取的内容是:这些文件中的内容哦!

数据操作流DataOutputStream、DataInputStream类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
 
public class DataOutputStreamDemo{
 public static void main(String[] args) throws IOException{
 File file = new File("d:" + File.separator + "hello.txt");
 char[] ch = { 'A', 'B', 'C' };
 DataOutputStream out = null;
 out = new DataOutputStream(new FileOutputStream(file));
 for(char temp : ch){
 out.writeChar(temp);
 }
 out.close();
 }
}

A B C

现在我们在上面例子的基础上,使用DataInputStream读出内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
 
public class DataOutputStreamDemo{
 public static void main(String[] args) throws IOException{
 File file = new File("d:" + File.separator + "hello.txt");
 DataInputStream input = new DataInputStream(new FileInputStream(file));
 char[] ch = new char[10];
 int count = 0;
 char temp;
 while((temp = input.readChar()) != 'C'){
 ch[count++] = temp;
 }
 System.out.println(ch);
 }
}

【运行结果】:

AB

合并流 SequenceInputStream

SequenceInputStream主要用来将2个流合并在一起,比如将两个txt中的内容合并为另外一个txt。下面给出一个实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.SequenceInputStream;
 
/**
 * 将两个文本文件合并为另外一个文本文件
 * */
public class SequenceInputStreamDemo{
 public static void main(String[] args) throws IOException{
 File file1 = new File("d:" + File.separator + "hello1.txt");
 File file2 = new File("d:" + File.separator + "hello2.txt");
 File file3 = new File("d:" + File.separator + "hello.txt");
 InputStream input1 = new FileInputStream(file1);
 InputStream input2 = new FileInputStream(file2);
 OutputStream output = new FileOutputStream(file3);
 // 合并流
 SequenceInputStream sis = new SequenceInputStream(input1, input2);
 int temp = 0;
 while((temp = sis.read()) != -1){
 output.write(temp);
 }
 input1.close();
 input2.close();
 output.close();
 sis.close();
 }
}

【运行结果】

结果会在hello.txt文件中包含hello1.txt和hello2.txt文件中的内容。

文件压缩 ZipOutputStream类

先举一个压缩单个文件的例子吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
 
public class ZipOutputStreamDemo1{
 public static void main(String[] args) throws IOException{
 File file = new File("d:" + File.separator + "hello.txt");
 File zipFile = new File("d:" + File.separator + "hello.zip");
 InputStream input = new FileInputStream(file);
 ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(
 zipFile));
 zipOut.putNextEntry(new ZipEntry(file.getName()));
 // 设置注释
 zipOut.setComment("hello");
 int temp = 0;
 while((temp = input.read()) != -1){
 zipOut.write(temp);
 }
 input.close();
 zipOut.close();
 }
}

【运行结果】

运行结果之前,我创建了一个hello.txt的文件,原本大小56个字节,但是压缩之后产生hello.zip之后,居然变成了175个字节,有点搞不懂。

不过结果肯定是正确的,我只是提出我的一个疑问而已。

上面的这个例子测试的是压缩单个文件,下面的们来看看如何压缩多个文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
 
/**
 * 一次性压缩多个文件
 * */
public class ZipOutputStreamDemo2{
 public static void main(String[] args) throws IOException{
 // 要被压缩的文件夹
 File file = new File("d:" + File.separator + "temp");
 File zipFile = new File("d:" + File.separator + "zipFile.zip");
 InputStream input = null;
 ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(
 zipFile));
 zipOut.setComment("hello");
 if(file.isDirectory()){
 File[] files = file.listFiles();
 for(int i = 0; i < files.length; ++i){
 input = new FileInputStream(files[i]);
 zipOut.putNextEntry(new ZipEntry(file.getName()
 + File.separator + files[i].getName()));
 int temp = 0;
 while((temp = input.read()) != -1){
 zipOut.write(temp);
 }
 input.close();
 }
 }
 zipOut.close();
 }
}

【运行结果】

先看看要被压缩的文件吧:

接下来看看压缩之后的:

大家自然想到,既然能压缩,自然能解压缩,在谈解压缩之前,我们会用到一个ZipFile类,先给一个这个例子吧。java中的每一个压缩文件都是可以使用ZipFile来进行表示的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.io.File;
import java.io.IOException;
import java.util.zip.ZipFile;
 
/**
 * ZipFile演示
 * */
public class ZipFileDemo{
 public static void main(String[] args) throws IOException{
 File file = new File("d:" + File.separator + "hello.zip");
 ZipFile zipFile = new ZipFile(file);
 System.out.println("压缩文件的名称为:" + zipFile.getName());
 }
}

【运行结果】:

压缩文件的名称为:d:\hello.zip

现在我们呢是时候来看看如何加压缩文件了,和之前一样,先让我们来解压单个压缩文件(也就是压缩文件中只有一个文件的情况),我们采用前面的例子产生的压缩文件hello.zip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
 
/**
 * 解压缩文件(压缩文件中只有一个文件的情况)
 * */
public class ZipFileDemo2{
 public static void main(String[] args) throws IOException{
 File file = new File("d:" + File.separator + "hello.zip");
 File outFile = new File("d:" + File.separator + "unZipFile.txt");
 ZipFile zipFile = new ZipFile(file);
 ZipEntry entry = zipFile.getEntry("hello.txt");
 InputStream input = zipFile.getInputStream(entry);
 OutputStream output = new FileOutputStream(outFile);
 int temp = 0;
 while((temp = input.read()) != -1){
 output.write(temp);
 }
 input.close();
 output.close();
 }
}

【运行结果】:

解压缩之前:


这个压缩文件还是175字节

解压之后产生:

又回到了56字节,表示郁闷。

现在让我们来解压一个压缩文件中包含多个文件的情况吧

ZipInputStream类

当我们需要解压缩多个文件的时候,ZipEntry就无法使用了,如果想操作更加复杂的压缩文件,我们就必须使用ZipInputStream类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
 
/**
 * 解压缩一个压缩文件中包含多个文件的情况
 * */
public class ZipFileDemo3{
 public static void main(String[] args) throws IOException{
 File file = new File("d:" + File.separator + "zipFile.zip");
 File outFile = null;
 ZipFile zipFile = new ZipFile(file);
 ZipInputStream zipInput = new ZipInputStream(new FileInputStream(file));
 ZipEntry entry = null;
 InputStream input = null;
 OutputStream output = null;
 while((entry = zipInput.getNextEntry()) != null){
 System.out.println("解压缩" + entry.getName() + "文件");
 outFile = new File("d:" + File.separator + entry.getName());
 if(!outFile.getParentFile().exists()){
 outFile.getParentFile().mkdir();
 }
 if(!outFile.exists()){
 outFile.createNewFile();
 }
 input = zipFile.getInputStream(entry);
 output = new FileOutputStream(outFile);
 int temp = 0;
 while((temp = input.read()) != -1){
 output.write(temp);
 }
 input.close();
 output.close();
 }
 }
}

【运行结果】:

被解压的文件:


解压之后再D盘下会出现一个temp文件夹,里面内容:

PushBackInputStream回退流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.PushbackInputStream;
 
/**
 * 回退流操作
 * */
public class PushBackInputStreamDemo{
 public static void main(String[] args) throws IOException{
 String str = "hello,rollenholt";
 PushbackInputStream push = null;
 ByteArrayInputStream bat = null;
 bat = new ByteArrayInputStream(str.getBytes());
 push = new PushbackInputStream(bat);
 int temp = 0;
 while((temp = push.read()) != -1){
 if(temp == ','){
 push.unread(temp);
 temp = push.read();
 System.out.print("(回退" + (char) temp + ") ");
 }else{
 System.out.print((char) temp);
 }
 }
 }
}

【运行结果】:

hello(回退,) rollenholt

1
2
3
4
5
6
7
8
/**
 * 取得本地的默认编码
 * */
public class CharSetDemo{
 public static void main(String[] args){
 System.out.println("系统默认编码为:" + System.getProperty("file.encoding"));
 }
}

【运行结果】:

系统默认编码为:GBK

乱码的产生:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
 
/**
 * 乱码的产生
 * */
public class CharSetDemo2{
 public static void main(String[] args) throws IOException{
 File file = new File("d:" + File.separator + "hello.txt");
 OutputStream out = new FileOutputStream(file);
 byte[] bytes = "你好".getBytes("ISO8859-1");
 out.write(bytes);
 out.close();
 }
}

【运行结果】:

??

一般情况下产生乱码,都是由于编码不一致的问题。

对象的序列化

对象序列化就是把一个对象变为二进制数据流的一种方法。

一个类要想被序列化,就行必须实现java.io.Serializable接口。虽然这个接口中没有任何方法,就如同之前的cloneable接口一样。实现了这个接口之后,就表示这个类具有被序列化的能力。

先让我们实现一个具有序列化能力的类吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.io.*;
/**
 * 实现具有序列化能力的类
 * */
public class SerializableDemo implements Serializable{
 public SerializableDemo(){
 
 }
 public SerializableDemo(String name, int age){
 this.name=name;
 this.age=age;
 }
 @Override
 public String toString(){
 return "姓名:"+name+" 年龄:"+age;
 }
 private String name;
 private int age;
}

这个类就具有实现序列化能力,

在继续将序列化之前,先将一下ObjectInputStream和ObjectOutputStream这两个类

先给一个ObjectOutputStream的例子吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.io.Serializable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
 
/**
 * 实现具有序列化能力的类
 * */
public class Person implements Serializable{
 public Person(){
 
 }
 
 public Person(String name, int age){
 this.name = name;
 this.age = age;
 }
 
 @Override
 public String toString(){
 return "姓名:" + name + " 年龄:" + age;
 }
 
 private String name;
 private int age;
}
/**
 * 示范ObjectOutputStream
 * */
public class ObjectOutputStreamDemo{
 public static void main(String[] args) throws IOException{
 File file = new File("d:" + File.separator + "hello.txt");
 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
 file));
 oos.writeObject(new Person("rollen", 20));
 oos.close();
 }
}

【运行结果】:

当我们查看产生的hello.txt的时候,看到的是乱码,呵呵。因为是二进制文件。

虽然我们不能直接查看里面的内容,但是我们可以使用ObjectInputStream类查看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.File;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
 
/**
 * ObjectInputStream示范
 * */
public class ObjectInputStreamDemo{
 public static void main(String[] args) throws Exception{
 File file = new File("d:" + File.separator + "hello.txt");
 ObjectInputStream input = new ObjectInputStream(new FileInputStream(
 file));
 Object obj = input.readObject();
 input.close();
 System.out.println(obj);
 }
}

【运行结果】

姓名:rollen 年龄:20

到底序列化什么内容呢?

其实只有属性会被序列化。

Externalizable接口

被Serializable接口声明的类的对象的属性都将被序列化,但是如果想自定义序列化的内容的时候,就需要实现Externalizable接口。

当一个类要使用Externalizable这个接口的时候,这个类中必须要有一个无参的构造函数,如果没有的话,在构造的时候会产生异常,这是因为在反序列话的时候会默认调用无参的构造函数。

现在我们来演示一下序列化和反序列话:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package IO;
 
import java.io.Externalizable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
 
/**
 * 序列化和反序列化的操作
 * */
public class ExternalizableDemo{
 public static void main(String[] args) throws Exception{
 ser(); // 序列化
 dser(); // 反序列话
 }
 
 public static void ser() throws Exception{
 File file = new File("d:" + File.separator + "hello.txt");
 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(
 file));
 out.writeObject(new Person("rollen", 20));
 out.close();
 }
 
 public static void dser() throws Exception{
 File file = new File("d:" + File.separator + "hello.txt");
 ObjectInputStream input = new ObjectInputStream(new FileInputStream(
 file));
 Object obj = input.readObject();
 input.close();
 System.out.println(obj);
 }
}
 
class Person implements Externalizable{
 public Person(){
 
 }
 
 public Person(String name, int age){
 this.name = name;
 this.age = age;
 }
 
 @Override
 public String toString(){
 return "姓名:" + name + " 年龄:" + age;
 }
 
 // 复写这个方法,根据需要可以保存的属性或者具体内容,在序列化的时候使用
 @Override
 public void writeExternal(ObjectOutput out) throws IOException{
 out.writeObject(this.name);
 out.writeInt(age);
 }
 
 // 复写这个方法,根据需要读取内容 反序列话的时候需要
 @Override
 public void readExternal(ObjectInput in) throws IOException,
 ClassNotFoundException{
 this.name = (String) in.readObject();
 this.age = in.readInt();
 }
 
 private String name;
 private int age;
}

【运行结果】:

姓名:rollen 年龄:20

本例中,我们将全部的属性都保留了下来,

Serializable接口实现的操作其实是吧一个对象中的全部属性进行序列化,当然也可以使用我们上使用是Externalizable接口以实现部分属性的序列化,但是这样的操作比较麻烦,

当我们使用Serializable接口实现序列化操作的时候,如果一个对象的某一个属性不想被序列化保存下来,那么我们可以使用transient关键字进行说明:

下面举一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package IO;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
/**
 * 序列化和反序列化的操作
 * */
public class serDemo{
 public static void main(String[] args) throws Exception{
 ser(); // 序列化
 dser(); // 反序列话
 }
 
 public static void ser() throws Exception{
 File file = new File("d:" + File.separator + "hello.txt");
 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(
 file));
 out.writeObject(new Person1("rollen", 20));
 out.close();
 }
 
 public static void dser() throws Exception{
 File file = new File("d:" + File.separator + "hello.txt");
 ObjectInputStream input = new ObjectInputStream(new FileInputStream(
 file));
 Object obj = input.readObject();
 input.close();
 System.out.println(obj);
 }
}
 
class Person1 implements Serializable{
 public Person1(){
 
 }
 
 public Person1(String name, int age){
 this.name = name;
 this.age = age;
 }
 
 @Override
 public String toString(){
 return "姓名:" + name + " 年龄:" + age;
 }
 
 // 注意这里
 private transient String name;
 private int age;
}

【运行结果】:

姓名:null 年龄:20

最后在给一个序列化一组对象的例子吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
/**
 * 序列化一组对象
 * */
public class SerDemo1{
 public static void main(String[] args) throws Exception{
 Student[] stu = { new Student("hello", 20), new Student("world", 30),
 new Student("rollen", 40) };
 ser(stu);
 Object[] obj = dser();
 for(int i = 0; i < obj.length; ++i){
 Student s = (Student) obj[i];
 System.out.println(s);
 }
 }
 
 // 序列化
 public static void ser(Object[] obj) throws Exception{
 File file = new File("d:" + File.separator + "hello.txt");
 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(
 file));
 out.writeObject(obj);
 out.close();
 }
 
 // 反序列化
 public static Object[] dser() throws Exception{
 File file = new File("d:" + File.separator + "hello.txt");
 ObjectInputStream input = new ObjectInputStream(new FileInputStream(
 file));
 Object[] obj = (Object[]) input.readObject();
 input.close();
 return obj;
 }
}
 
class Student implements Serializable{
 public Student(){
 
 }
 
 public Student(String name, int age){
 this.name = name;
 this.age = age;
 }
 
 @Override
 public String toString(){
 return "姓名: " + name + " 年龄:" + age;
 }
 
 private String name;
 private int age;
}

【运行结果】:

姓名: hello 年龄:20

姓名: world 年龄:30

姓名: rollen 年龄:40

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics