Java中的IO框架流一

1      IONo18

1.1    IO框架

IO:Input    Output

在程序运行的过程中,可能需要对一些设备进行数据的读取操作,比如:对磁盘进行读写操作。

IO框架就是用于对设备进行读写操作,在学习对象之前,我们首先要学习一个非常重要的类:File类

IO框架

File类

概述:用于描述存在或不存在的文件或文件夹

绝对路径:完整的路径名

相对路径:相对于当前所在的路径

IO流:

主要用于实现设备之间的数据传输

设备:内存、磁盘、网络、键盘、鼠标…….

?《在Java中,IO流的方向是相对内存而言的》

设备之间的数据传输通过流来实现

流的分类,按照流向、功能可以分为以下三种:

1:按方向分类:输入流、输出流

在java中,IO流的方向是相对内存而言的

2:按单位分类:字节流、字符流

1)在磁盘上存储数据都是以二进制的形式来保存,但是每一位只能保存两个状态,为了能够表示足够多的状态,所以我们以8位为一个单位,叫做字节,所以在内存中保存数据的最小单位是字节

在程序中数据存储的最小单位是字节(byte)

2)磁盘上保存数据都是以二进制形式,如果需要在磁盘上保存一些字符(a,b),就需要使用编码表先将子符进行编码,再将对应的编码保存在磁盘上,不同的编码表保存数据所用的字节数是不同的,通过是在1~3个字节来保存,如果以字节为单位处理数据,那么就有可能出现数据不完整和乱码的情况,所以在处理文本数据的时候建议使用字符流。

总结:如果处理的数据是文本数据,一般就用字符流,其他数据一般使用字节流(字节流也能操作文本数据)。

3:按功能分为:节点符、过滤流(处理流)

在IO框架中有四个顶层基类:

1:字符流:Reader   Writer

2:字节流:InputStream  OutputStream

乱码的成因:读取数据的时候和写入数据的时候所用的编码不一致

自译:【1:三种文件表示形式:

File f = new File("g:/day18.txt")

File f2 = new File("g:\\prop.txt");

File f3 = new File("g:"+File.separator+"file.txt");

f3.exists():判断文件是否存在f3.createNewFile();//创建这个对象所表示的文件f.delete();//用于删除指定的文件

【2:创建实际的目录

File f2 = new File("g:/practice2");    f2.mkdir();

3:生成一个生层次的目录结构:Filefolder = new File("g:/a/b/d/c/d/e/f/g");   folder.mkdirs();

【4:返回目录下所有的文件

File[] files = folder.listFiles();

for (File file : files) {//循环遍历这个文件数组

System.out.println(file.getPath());  //打印出每个文件的路径

Date d=newDate();

d.setTime(file.lastModified());    //最后一次修改的时间

System.out.println(file.getName()+","+d.toString());//打印每个文件的名字

}

5过滤某文件夹下的文件:

//后缀过滤器(文件过滤器)

class SuffixFilter implements FileFilter {

@Override

//是一个回调方法,在listFiles时回调

publicboolean accept(File file) {

//如果文件以.zip结尾并且不是目录

if(file.getName().endsWith(".zip") && file.isFile()) {

returntrue; //返回true,表示接受这个文件,保留下来

}

returnfalse; //返回false,表示不接受,过滤掉

}

}

调用:

File folder = new File("g:/" + File.separator +"千锋课堂软件");

//列出....下文件,过滤标准是从参数传入的后缀过滤器对象

File[]files = folder.listFiles(new SuffixFilter());

for(File f : files) { //遍历过滤后的文件数组

System.out.println(f.getName());

}

使用匿名内部类实现文件过滤:

File folder = newFile("g:"+File.separator+"手头查询文档");

File[]files = folder.listFiles(new FileFilter() {

//这个方法会在listFiles时被回调,来判断每个文件是保留还是丢弃

@Override

publicboolean accept(File file) {

//如果以"张"开头并且是文件

if(file.getName().startsWith("大话")&&file.isFile()){

returntrue;   //保留

}

returnfalse;   //否则过滤掉

}

});

for(File file : files) {

System.out.println(file.getName());

}

6:递归调用遍历文件,区分文件和目录

Filefolder = new File("g:"+File.separator+"a");

showAll(folder);

}

//如果是目录,显示目录中所有文件,如果是一个文件,直接打印名字

privatestatic void showAll(File file) {

if(file.isFile()){  //如果是文件

System.out.println(file.getName());//打印出名字

}else{//如果是目录

File[]files = file.listFiles();   //列出所有文件、目录

for(File f : files) {//遍历每个文件、目录

showAll(f);       //对每个文件、目录再进行递归调用

}

}

}

7:文件的复制

FileInputStream fis = newFileInputStream("G:/杜维瀚-我就喜欢你这样的丫头.zip");

FileOutputStreamfos = new FileOutputStream("G:/徐杜维瀚-我就喜欢你这样的丫头.zip");

intn = 0;

while((n = fis.read()) != -1) { //每次读一个字节//从a文件中读,写到b文件中

fos.write(n);//写到另一个文件中

System.out.println("复制完成!");

fos.close();

fis.close();

}

带缓存形式复制文件

FileInputStream fis = newFileInputStream("g:/韩红-一个人.zip");

FileOutputStreamfos = new FileOutputStream("g:/有韩红-一个人.zip");

byte[]buf = new byte[1024];    //缓冲

intn= 0;   //保存返回信息

//这种方式拷贝1024个字节只操作一次磁盘,效率较高

while((n=fis.read(buf))!=-1){//循环读数据到缓存中

fos.write(buf,0, n);    //将缓存中有效数据写到文件中

}

fos.close();

fis.close();

1.2    文件

1.2.1  创建文件的几种方式

//方式一

File file = newFile("g:\\demo.txt");

方式二

Filefile2 = new File("g:\\", "demo.txt");

//方式三

File dir = newFile("g:\\filetest");

File file3 = new File(dir,"demo.txt");

//File的成员字段

//路径分隔符,不同系统的路径分隔符可能不同,通过该静态常量可以获取到和系统相关的路径分隔符

System.out.println(File.pathSeparator);

System.out.println(File.pathSeparatorChar);

//名称分隔符,不同的系统名称分隔符不同

//注意:在windows下,应该使用“\”(反斜杠),但实际正反都支持

System.out.println(File.separator);

System.out.println(File.separatorChar);

Filefile = new File("G:\\filetest");

/*     //获取指定文件夹下的所有子文件(夹)的名称

String[] fileNames =file.list();

for (String fileName :fileNames) {

System.out.println(fileName);

}

//获取指定文件夹下所有的子文件(夹)的File对象

File[] files =file.listFiles();

for (File f : files) {

System.out.println(f.getAbsolutePath());

}*/

1.3    文件的操作方法

1.3.1  获取类

getName()获取文件名

listRoots()获取所有磁盘的根目录(静态方法)

getTotalSpace()获取当前所在磁盘的总大小(单位:字节)

《//磁盘的总大小

System.out.println(file3.getTotalSpace()/1024/1024/1024+"GB");》

getFreeSpace()获取当前所在磁盘的剩余空间(单位:字节)

lastModified()获取最后修改时间(1970到现在所经过的毫秒值)

《SimpleDateFormatsdf = new SimpleDateFormat("yyyy年MM月dd日HH:mm:ss");

System.out.println(sdf.format(file3.lastModified()));》

length()获取文件的大小(单位:字节)

getParent()获取父目录的路径

getPath()获取相对路径

getAbsolutePath()获取绝对路径

1.3.2  创建和删除类

delete()删除文件或文件夹

mkdirs()创建多级目录

mkdir()创建文件夹(创建成功,返回true,否则返回false)

createNewFile()创建文件(如果文件不存在,就创建,如果存在,就不创建并返回false

1.3.3  判断类方法

isHidden()判断是否是隐藏文件

isFile()判断是否是文件

isDirectory()判断是否是目录

exists()判断是否存在

1.3.4  重命名类方法

renameTo(Filedest)移动并改名

1.3.5  使用文件过滤器

listFiles(FileFilterfilter)

listFiles(FilenameFilterfilter)

list(FilenameFilterfilter)

FilenameFilter:文件名过滤器接口

FileFilter:文件过滤器接口

文件名过滤器,实现接口

classMyFileNameFilter implements FilenameFilter{

/**

*文件过滤的规则

*返回值得意思就是判断指定的文件是否通过过滤

*如果是true代表通过过滤

*/

@Override

public boolean accept(File dir, Stringname) {

System.out.println(dir.getAbsolutePath());

returnname.endsWith(".txt");

}

}

File[] files =file.listFiles(new MyFileNameFilter());

System.out.println("数组内容");

for (File f : files) {

System.out.println(f.getAbsolutePath());

}

匿名内部类文件过滤器

File[] files =file.listFiles(new FileFilter() {

@Override

public booleanaccept(File file) {

System.out.println(file.getAbsolutePath());

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

}

});

System.out.println("数组内容");

for (File f : files) {

System.out.println(f.getAbsolutePath());

}

1.3.6  递归实现文件操作

//2.输出一个文件夹(包括子目录)中的所有文件的名称(使用File和递归来实现)

public class Ex1 {

public static void main(String[] args){

File dir = newFile("g:\\anjavaworkspaseTest");

bianLi(dir);

}

/**

*遍历输出指定文件中的文件(夹)的名称

* @param dir

*/

private static void bianLi(File dir){

System.out.println(dir.getName());

//获取文件夹中的所有子文件(夹)

File[] files = dir.listFiles();

for (File f : files) {

if(f.isFile()){

System.out.println(f.getName());

}else{

bianLi(f);

}

}

}

}

递归删除文件

//      3.使用file类的delete方法和递归算法删除一个文件夹(带有内容)

public static void main(String[] args){

// TODO Auto-generated methodstub

File dir= new File("g:\\testDirFile");

deleterDir(dir);

}

//删除指定的文件(包含内容)

privatestatic void deleterDir(File dir) {

//写代码需要保证代码的健壮性

//入参检测

//删除文件中的所有内容

//如果File对象描述的是文件,调用listFiles方法后返回的是null

if(dir== null ||!dir.isDirectory()){

thrownew IllegalArgumentException("File对象不能为空,并且只能是文件");

}

File[]file = dir.listFiles();

for(File f : file) {

if(f.isFile()){

f.delete();

}else{

//如果是文件夹,就需要先删除文件中的内容,在删除文件

deleterDir(f);

}

}

//删除文件夹本身(空文件夹)

dir.delete();

}

❖  对文件的操作:

◦publicbooleancreateNewFile()//不存在时创建此文件对象所代表的空文件

◦publicbooleandelete()//删除文件。如果是目录必须是空才能删除

◦publicbooleanmkdir()//创建此抽象路径名指定的目录

◦publicbooleanmkdirs()//创建此抽象路径名指定的目录,包括所有必需但不存在的父目录

◦publicbooleanrenameTo(File dest)//重新命名此抽象路径名表示的文件

❖  浏览目录中的文件和子目录

◦publicString[]list()//返回此目录中的文件名和目录名的数组

◦publicFile[]listFiles()//返回此目录中的文件和目录的File实例数组

◦publicFile[]listFiles(FilenameFilter filter)//返回此目录中满足指定过滤器的文件和目录

●java.io.FilenameFilter接口:实现此接口的类实例可用于过滤文件名

将数据存储在磁盘上,断电不消失、程序执行完不消失(之前的大部分练习都将数据存在内存中,断电消失,程序执行完也会消失)

File 表示、描述文件、文件夹的类

构造方法: File(Stringpath)参数是文件的路径

如: "d:\\file.txt"、"d:/folder/file1.txt"

(如果要使用反斜杠,必须用转义符\\来表示;在高级的JDK版本中也可以直接用/来表示;另外File.separator可以自动根据运行的平台转换为"\\"或者"/")

注意:new一个File对象只会在内存中产生一个描述、表示某一个文件的对象,并不会创建这个文件本身

主要方法:

boolean exists()告诉我们这个对象所描述的文件究竟存不存在,存在返回true,不存在返回false

createNewFile()创建新的文件

boolean isFile()如果这个对象表示文件,返回true,否则返回false

boolean isDirectory()如果这个对象表示文件夹,返回true,否则返回false

mkdir()生成一个文件夹、目录

mkdirs()生成一个目录,如果要生成的目录的父目录不存在,会自动创建

File[] listFiles()返回一个目录中的所有文件,以文件数组的形式返回

getPath()获取文件路径

getName()获取文件名

delete()删除文件

boolean canExecute()是否可执行

boolean canRead()是否可读

boolean isHidden()是否是隐藏文件

File[]listFiles(FileFilter filter)返回目录下所有符合过滤器要求的文件的数组

FileFilter是一个接口,要使用它,就用一个类实现它并实现boolean accept(File file)方法,在这个方法里写上过滤的逻辑,符合条件要保留的就返回true,不符合条件要过滤掉的返回 false,参数里的file是要进行判断保留还是不保留的文件

然后实例化这个过滤器类,并传入listFiles()的参数,这样listFiles在列出文件时会依据这个过滤器的逻辑、标准去过滤文件

listFiles只能列一层目录

IO Input输入 Output输出

1、输入流、输出流

2、字节流、字符流

3、节点流、处理流

1.4    InputStream

字节文件读取:

FileInputStream fis = newFileInputStream("g:/output.txt");

while(true){

intn = fis.read();    //读一个字节

if(n== -1){//如果达到文件底部

break;

}

System.out.println((char)n);   //打印字节对应的字符

}

fis.close();

fis = new FileInputStream("g:/output.txt");

byte[]buf = new byte[4];  //创建长度为4的字节数组缓存

while(true){

//每次从文件中读取4个字节放入缓冲中

intn = fis.read(buf);

if(n== -1){     //读取到文件末尾了

break;       //退出

}//遍历缓冲区字节数组,循环条件用n,可以将最后一次的无效数据排除

for(int i=0;i

System.out.println((char)buf[i]);   //打印

}

FileInputStream fis = newFileInputStream("g:/output.txt");

byte[]buf = new byte[4];     //创建长度为4的字节数组缓冲

intn = 0;

while((n=fis.read(buf))!=-1){

for(inti=0;i

System.out.println((char)buf[i]);

}

}

fis.close();

InputStream抽象类,往里读信息、数据,以字节的形式

int read():读取单个字节(返回的是int类型)

int read(byte[] buf):读取多个字节,返回的是读取的字节的个数

public intread(byte[] b, int off, int len) throws IOException

public void close()throws IOException

public intavailable() throws IOException

public skip(long n)throws IOException

1.5    OutputStream

字节文件输出:

FileOutputStream fos = newFileOutputStream("g:/output.txt");//覆盖输出

FileOutputStreamfos = new FileOutputStream("g:/output.txt",true);//追加输出

fos.write(97);

fos.write(98);

fos.write(99);

fos.write(100);

fos.write(101);

fos.close();   //文件流操作结束后,一定要关闭

byte[] buffer ={65,66,67,68,69};  //数据源

try{

FileOutputStreamfos = new FileOutputStream("g:/output.txt");

//从buffer数组中截取下标1开始的3个字节,写到文件中

fos.write(buffer,1, 3);//相比下面的,这种只有写一次磁盘

/*fos.write(66);

fos.write(67);        //这种方式要写三次磁盘,要慢一点

fos.write(68);*/

fos.close();

OutputStream抽象类,往外写信息、数据,以字节的形式

write(int n)将指定的字节写入此输出流(写出单个字节)

write(byte[] buf,intoff,int len)将b.length个字节从指定的byte数组写入此输出流

public voidwrite(byte[] b, int off, int len) throws IOException将指定byte数组中从偏移量off开始的len个字节写入此输出流

public void flush()throws IOException刷新此输出流并强制写出所有缓冲的输出字节

刷新流的缓存

pulbic void close()throws IOException关闭此输出流并释放与此流有关的所有系统资源

FileInputStream文件输入流、FileOutputStream文件输出流,分别继承了InputStream和OutputStream,分别用来从文件中读数据和写数据

这里的输入和输出,参照物是程序、虚拟机

两个read()和两个write()是他们最重要的方法了。

1.6    FileOutputStream

构造方法FileOutputStream(String path)表示对参数路径的文件创建一个输出流,这个输出流可以用来对这个文件写数据

(如果这个文件不存在,会自动创建)

(最后一定要关闭文件流)

(FileOutputStream(Stringpath,boolean append)可以向原文件追加输出数据,不覆盖原来的信息)

write(int n)表示向输出流中写一个字节的数据

write(byte[] buf,intoff,int len)表示将buf数组中的字节从off位置开始,截取长度len的字节数据写到文件中

(buf是buffer缓冲,off是offset起点,len是length长度)

1.7    FileInputStream

构造方法FileInputStream(String path)表示对参数路径的文件创建一个输入流,可以用这个输入流读取这个文件的信息

int read()从输入流中一个一个字节读入,如果读到了文件底部,返回-1,否则返回那个读入的字节

int read(byte[] buf)从输入流中几个几个字节读入,每次读取的字节数,由参数中的字节数组长度决定;每次读取出来的这些字节也都按顺序存放在这个字节数组中

如果读到文件底部,返回-1;否则返回实际读的有效字节数

相比无参read(),单参read()每次操作磁盘读多个字节,效率更高

在文件流处理中,关闭流的操作原则上要放在finally中,以确保执行;但是在学习过程中可以先关注重点,把这个问题暂时忽略

1.8    字符流

文件输入流读取文件:Reader

FileReader reader = new FileReader("g:/output.txt");

intn = 0;

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

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

}

reader.close();

文件输出流,从程序中对外写出文件:

FileWriter writer = newFileWriter("g:/output.txt);         //覆盖的形式

FileWriterwriter = new FileWriter("g:/output.txt",true);    //追加的形式

writer.write("敢做敢当,敢拼才会赢!");

writer.write("1000phone");

writer.write("1000phone".toCharArray(),0, "1000phone".toCharArray().length);

writer.close();

for (int i= 1; i <= 10; i++) {

FileInputStreamfis = new FileInputStream("G:/fos.txt");

FileOutputStreamfos = new FileOutputStream("g:/home1" + i + ".txt");

intn = 0;

byte[]buf = new byte[1024];

while((n = fis.read(buf)) != -1) {

fos.write(buf,0, n);

}

fos.close();

fis.close();

}

File folder = new File("g:/home1");

File[]files = folder.listFiles();

byte[]bytes = new byte[16];

for(File file : files) {

System.out.println(file.getName());

FileInputStreamfis = new FileInputStream(file);

intn = fis.read(bytes);

for(inti=0;i

System.out.println(bytes[i]+ " ");

}

System.out.println();

fis.close();

}

字符流Reader   Writer

FileReader     FileWriter

FileReader文件字符输入流

构造方法FileReader(String path)在path路径对应的文件上创建一个文件字符输入流

int read()读一个字符,返回-1表示读到尾部

int read(char[] cbuf)读若干个字符到字符数组缓冲中,返回-1表示读到尾部,否则返回实

际读的字符个数

Close();

FileWriter文件字符输出流

构造方法FileWriter(String path)在path路径对应的文件上创建一个文件字符输出流

write(String s)

write(String s , intoff,int len)

writer(char[] crs)

close();//关闭流,自动刷新缓存注意:一旦流被关闭后就不能再使用,会抛出异常

flush();

write(int c)写一个字符

write(char[] cbuf,intoff,int len)将参数字符缓冲从off开始截取,截取长度len的字符,写到字符输出流中

IO异常处理的注意事项

1、先创建的资源后释放

1、  释放资源的操作必须放在flnally代码块中执行

2、

publicint read() throws IOException读取单个字符,返回作为整数读取的字符,如果已到达流的末尾返回-1

publicint read(char[] cbuf) throws IOException 将字符读入数组,返回读取的字符数

publicabstract int read(char[] cbuf, int off, int len) throwsIOException

publicabstract void close() throws IOException 关闭该流并释放与之关联的所有资源

publiclong skip(long n) throws IOException 跳过n个字符。

public voidwrite(int c) throws IOException写入单个字符

public voidwrite(char[] cbuf) throws IOException写入字符数组

public abstract voidwrite(char[] cbuf, int off, int len) throws IOException写入字符数组的某一部分

public voidwrite(String str) throws IOException写入字符串

public voidwrite(String str, int off, int len) throws IOException写字符串的某一部分

public abstract voidclose() throws IOException关闭此流,但要先刷新它

public abstract voidflush() throws IOException刷新该流的缓冲,将缓冲的数据全写到目的地

还提供了一个FileWriter(String fileName,boolean append)构造方法来指定是否使用追加模式

总结:

❖  File类

❖  Java流类的分类

◦      输入流和输出流、字节流和字符流、节点流和过滤(处理)流

❖  文件流

◦      FileInputStream/FileOutputStream

◦      FileReader/FileWriter

❖  缓冲流

◦      BufferedInputStream/BufferedOutputStream

◦      BufferedReader/BufferedWriter

❖  转换流:InputStreamReader/OutputStreamWriter

❖  数据流:DataInputStream/DataOutputStream

❖  压缩流:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容