当前位置:   article > 正文

Java知识:IO流_java bufferedreader(new inputstreamreader)java.io.

java bufferedreader(new inputstreamreader)java.io.ioexception:error

Java中IO流的体系结构如图:

这里写图片描述

Java流类的类结构图:

这里写图片描述

主要的类如下:

 1. File(文件特征与管理):用于文件或者目录的描述信息,例如生成新目录,修改文件名,删除文件,判断文件所在路径等。
 2. InputStream(二进制格式操作):***抽象类***,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。
 3. OutputStream(二进制格式操作):***抽象类***。基于字节的输出操作。是所有输出流的父类。定义了所有输出流都具有的共同特征。
 4.Reader(文件格式操作):***抽象类***,基于字符的输入操作。
 5. Writer(文件格式操作):***抽象类***,基于字符的输出操作。
 6. RandomAccessFile(随机文件操作):一个独立的类,直接继承至Object.它的功能丰富,可以从文件的任意位置进行存取(输入输出)操作。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

接下来结合例子理解各个IO流

FileInputStream

/**
 * 字节流
 *读文件
 * */
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));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

DataOutputStream与DataInputStream

import java.io.DataOutputStream ;  
import java.io.File ;  
import java.io.FileOutputStream ;  
public class DataOutputStreamDemo{  
    public static void main(String args[]) throws Exception{    // 所有异常抛出  
        DataOutputStream dos = null ;           // 声明数据输出流对象  
        File f = new File("d:" + File.separator + "order.txt") ; // 文件的保存路径  
        dos = new DataOutputStream(new FileOutputStream(f)) ;   // 实例化数据输出流对象  
        String names[] = {"衬衣","手套","围巾"} ; // 商品名称  
        float prices[] = {98.3f,30.3f,50.5f} ;      // 商品价格  
        int nums[] = {3,2,1} ;  // 商品数量  
        for(int i=0;i<names.length;i++){ // 循环输出  
            dos.writeChars(names[i]) ;  // 写入字符串  
            dos.writeChar('\t') ;   // 写入分隔符  
            dos.writeFloat(prices[i]) ; // 写入价格  
            dos.writeChar('\t') ;   // 写入分隔符  
            dos.writeInt(nums[i]) ; // 写入数量  
            dos.writeChar('\n') ;   // 换行  
        }  
        dos.close() ;   // 关闭输出流  
    }  
};  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
import java.io.DataInputStream ;  
import java.io.File ;  
import java.io.FileInputStream ;  
public class DataInputStreamDemo{  
    public static void main(String args[]) throws Exception{    // 所有异常抛出  
        DataInputStream dis = null ;        // 声明数据输入流对象  
        File f = new File("d:" + File.separator + "order.txt") ; // 文件的保存路径  
        dis = new DataInputStream(new FileInputStream(f)) ; // 实例化数据输入流对象  
        String name = null ;    // 接收名称  
        float price = 0.0f ;    // 接收价格  
        int num = 0 ;   // 接收数量  
        char temp[] = null ;    // 接收商品名称  
        int len = 0 ;   // 保存读取数据的个数  
        char c = 0 ;    // '\u0000'  
        try{  
            for(int i=0;i<3;i++){       //如果是while(true)是会产生EOFException的!!!!
                temp = new char[200] ;  // 开辟空间  
                len = 0 ;  
                while((c=dis.readChar())!='\t'){    // 接收内容  
                    temp[len] = c ;  
                    len ++ ;    // 读取长度加1  
                }  
                name = new String(temp,0,len) ; // 将字符数组变为String  
                price = dis.readFloat() ;   // 读取价格  
                dis.readChar() ;    // 读取\t  
                num = dis.readInt() ;   // 读取int  
                dis.readChar() ;    // 读取\n  
                System.out.printf("名称:%s;价格:%5.2f;数量:%d\n",name,price,num) ;  
            }  
        }catch(Exception e){e.printStackTrace;}   //原作者没有写e.printStackTrace;这句话
        dis.close() ;  
    }  
};  
  • 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

经过这个例子会对这个类有所理解,下面这两句话对理解这个类很有帮助:
DataInputStream是数据输入流,可以读取java的基本数据类型。
FileInputStream是从文件系统中,读取的单位只能是字节。

ByteArrayInputStream+PushBackInputStream

/**
 * 回退流操作
 * */
public class PushBackInputStreamDemo{
    public static void main(String[] args) throwsIOException{
       public static void main(String[] args) throws Exception
             { 
               String s="123456789";
               byte b[]=s.getBytes();
               BufferedInputStream bis=new BufferedInputStream(new ByteArrayInputStream(b));
               PushbackInputStream pis=new PushbackInputStream(bis);
               int tmp=1;
               int i=0;
               while((tmp=pis.read())!=-1)
               {   


               if(tmp=='3')                 //监听
               {
                   pis.unread('n');         //修改
                   tmp=pis.read();          //修改后的值保存到变量tmp
               }

               System.out.print((char)tmp);

               i++;
               }
               pis.close();
             } 
    }
}
  • 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

ByteArrayInputStream基本的介质流之一,它从Byte数组中读取数据。
FIleInputStream也是基本的介质流之一,它从文件中读取数据。
PushBackInputStream的作用:监听流中的数据,如果监听到自己感兴趣的数据可以对其进行改写。

InputStream与BufferedInputStream比较


public class DataInputStreamDemo {
    private static final String FILENAME="E:\\迅雷下载\\越狱.Prison.Break.S05E06.中英字幕.HDTVrip.720P.mp4";
    public static void main(String[] args) throws IOException {
        long l1 = readByBufferedInputStream();
        long l2 = readByInputStream();
        System.out.println("通过BufferedInputStream读取用时:"+l1+";通过InputStream读取用时:"+l2);
  //      System.out.println("通过BufferedInputStream读取用时:"+l1);
    }

    public static long readByInputStream() throws IOException {
        InputStream in=new FileInputStream(FILENAME);
        byte[] b=new byte[8192];
        int l=0;
        long start=System.currentTimeMillis();
        while(in.read(b)!=-1){
        }
        long end=System.currentTimeMillis();
        return end-start;
    }

    public static long readByBufferedInputStream() throws IOException {
        BufferedInputStream in=new BufferedInputStream(new FileInputStream(FILENAME));
        byte[] b=new byte[8192];
        int l=0;
        long start=System.currentTimeMillis();
        while(in.read(b)!=-1){
        }
        long end=System.currentTimeMillis();
        return end-start;
    }           
}
  • 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

上面的程序我们再多测试几种情况:
两个new byte[8192]的情况下:
通过BufferedInputStream读取用时:325;通过InputStream读取用时:320
两个new byte[4096]的情况下:
通过BufferedInputStream读取用时:356;通过InputStream读取用时:501
两个new byte[1024]的情况下:
通过BufferedInputStream读取用时:355;通过InputStream读取用时:1467
两个new byte[512]的情况下:
通过BufferedInputStream读取用时:370;通过InputStream读取用时:2847
两个new byte[256]的情况下:
通过BufferedInputStream读取用时:374;通过InputStream读取用时:5457
我们发现BufferedInputStream的优势显现出来了,快了10多倍。

疑问1:为什么BufferedInputStream读取时间没什么变化?

其实明确一点:读取数据的快慢和程序访问磁盘的次数(IO操作)是有极大关系的,可以说是主要影响因素。
BufferedInputStream自身维护了一个默认8192字节(8K)的缓冲区,这个缓冲区有什么用呢?
如果一份文件8M,那么程序访问了8M/8=1000次
new byte[8192],new byte[4096],new byte[1024],new byte[512],new byte[256]是程序自己维护的数组,数来存储缓冲区数据,8K缓冲区仍然没有改变,那么访问磁盘次数没变,所以时间大体上是相同的。
也可以说是此例子中的BufferedInputStream类自身维护了一个8K缓冲区,程序员又为他提供了一个new Byte数组来记录缓冲区中的数据。8K的缓冲区不变,读取时间不会有很大变化。

疑问2:为什么FileInputStream读取时间变化那么大?

FileInputStream没有缓冲区,我们是通过创建一个New Byte数组来模拟缓冲区。
new byte[8192],new byte[4096],new byte[1024],new byte[512],new byte[256]不同的byte数组会使得访问磁盘次数产生很大变化,进而影响读取时间,所以时间变化大而且细心的会发现呈现指数变化。

疑问2:为什么8K的时候BufferedInputStream比FIleInputStream速度还慢?

两个类都有8K缓冲区:
BufferedInputStream类自身维护的8K缓冲区
我们为FileInputStream模拟的8Kbyte数组
所以他们访问磁盘的次数相同,但是BufferedInputStream不是单纯的维护一个数组,还要维护数组中其他“东西”(我也不想弄清楚有啥)所以他的操作慢一些。
关键是弄明白两个new byte的作用!
BufferedInputStream中的new byte[]是存储缓冲区中的数据,属于内存操作,速度快,对读取时间影响小
FileInputStream中的new byte[]起到的是缓冲区的作用,属于IO操作,速度慢,对读取时间影响大。

FileInputStream与FileOutputStream复制MP3的例子来唤醒对这两个类的记忆

FileInputStream fis = new FileInputStream("c:\\0.mp3"); 
FileOutputStream fos = new FileOutputStream("c:\\1.mp3");
byte[] buf=new byte[1024];
int len=0;
while((len=fis.read())!=-1)
{
    fos.write(buf,0,len);
}
fos.close();
fis.close();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

BufferedInputStream与BufferedOutputStream复制MP3来唤醒对这两个类的记忆

FileInputStream fis = new FileInputStream("c:\\0.mp3"); 
BufferedInputStream bis=new BufferedInputStream(fis);

FileOutputStream fos = new FileOutputStream("c:\\1.mp3");
BufferedOutputStream bos=new BufferedOutputStream(fos);
byte[] buf=new byte[1024];          //这句话对效率的提升也有很大帮助,不光需要BufferedInputStream、BufferedOutputStream的内部缓冲区来提升效率。
int len=0;
while((len=fis.read())!=-1)
{
    fos.write(buf,0,len);
}
fos.close();
fis.close();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

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

/**
 * 使用内存操作流将一个大写字母转化为小写字母
 * */
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);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

PipedInputStream+PipedOutputStream实现线程间通讯

public class DataInputStreamDemo {
    //private static final String FILENAME="E:\\迅雷下载\\越狱.Prison.Break.S05E06.中英字幕.HDTVrip.720P.mp4";
    public static void main(String[] args) throws IOException {
        PipedInputStream input=new PipedInputStream();
        PipedOutputStream output=new PipedOutputStream();
        input.connect(output);
        new Thread(new Input(input)).start();
        new Thread(new Output(output)).start();
    }

}
class Input implements Runnable
{
    private PipedInputStream in;
    Input(PipedInputStream in)
    {
        this.in=in;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        try {
            byte[] b=new byte[1024];
            int len=in.read(b);
            String s=new String(b,0,len);

            System.out.println("s="+s);
            in.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
    class Output implements Runnable
    {
        private PipedOutputStream out;
        Output(PipedOutputStream out)
        {
            this.out=out;
        }
        @Override
        public void run() {
            // TODO Auto-generated method stub
            try {
                //Thread.sleep(5000);
                out.write("hi.管道来了".getBytes());
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } 
        }



}
  • 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

控制台输出:
s=hi.管道来了
此流不建议用于单线程,可能会引起死锁。因为read方法是阻塞式方法,在单线程中读取不到数据会一直等待,write方法执行不到。

FileReader与FileWriter例程

public class Test
{
    private static final int BUFFER_SIZE=1024;
    public static void main()
    {
        FileReader fr=null;
        FileWriter fw=null;
        try
        {
            fr=new FileReader("1.txt");
            fw=new FileWriter("2.txt");
            //创建一个临时容器,用于缓存读取到的字符
            char[] buf=new char[BUFFER_SIZE];//这就是缓冲区
            //定义一个变量记录读取到的字符数,(其实就是往数组里装的字符个数)
            int len=0;
            while((len=fr.read(buf))!=-1)
            {
                fw.write(buf,0,len);
            }
        }catch(Exception e)
        {
            throw new RuntimeException("读写失败");
        }finally
        {
            if(fw!=null)
            try
            {
            fw.close();
            }catch(Exception)
            {
            e.printStackTrace();
            }
            if(fr!=null)
            try
            {
            fr.close();
            }catch(Exception)
            {
            e.printStackTrace();
            }
        }
    }
}
  • 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

BufferedReader与BufferedWriter例程

public class test
{
    public static void main()
    {
        FileReader fr=new FileReader("1.txt");
        BufferedReader bufr=new BufferedReader(fr);
        FileWriter fw=new FileWriter("2.txt");
        BufferedWriter bufw=new BufferedWriter(fw);
        String line=null;
        while((line=bufr.readLine())!=null)
        {
        bufw.write(line);
        bufw.newLine();
        bufw.flush();
        }
        bufr.close();
        bufw.close();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

转换流:InputStreamReader与OutputStreamWriter例程:控制台输入字符串,控制台显示字符串

public class test
{
 public static void mian(String[] args)throws IOException
 {
     //控制台输入
    InputStream in=System.in;
    //通过转换流转换成字符数据,方便操作
    InputStreamReader isr=new InputStreamReader(in);
    //加入缓冲更高效
    BufferedReader bufr=new BufferedReader(isr);
    OutputStream out=System.out;
    OutputSteamWriter osw=new OutputStream(out);
    BufferedWriter bufw=new BufferedWriter(osw);
    String line=null;
    while((line=bufr.readLine())!=null)
    {
        if("over".equals(line))
        break;
        bufw.write(line.toupperCase());
        bufw.newLine();
        bufw.flush();
    }
 }
}

  • 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

不使用转换流也可以完成上述功能,本例子就是为了加深转换流的印象才用的转换流。

什么情况下必须用转换流呢?

当需要明确码表的时候

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

        //在IO流中,如果想指定编码读写数据,只能使用转换流。
        //采用指定编码从文本文件中读取内容
        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("C:\\a.txt"), "UTF-8"));
        String line = null;
        while ((line=br.readLine())!=null) {
            System.out.println(line);
        }
        br.close();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
public static void main(String[] args) throws IOException {

        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("C:\\a.txt"), "UTF-8"));
        bw.write("I am 。。");
        bw.close();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

IO流操作规律

想要知道开发时用到哪个对象。只要通过四个明确即可。
1、明确源和目的
源:InputStream Reader
目的:OutputStream Writer
2、明确数据是否是纯文本。
源:是纯文本:Reader
否:InputStream
目的:是纯文本:Writer
否:OutputStream
3、明确具体的设备:
源设备:
硬盘:File
键盘:System.in
内存:数组
网络:Socket流
目的设备:
硬盘:File
键盘:System.in
内存:数组
网络:Socket流
4、是否需要额外功能
1、是否需要高效(缓冲区)
是就加上buffer
2、转换

需求1:复制一个文本文件

1、明确源和目的:
源:InputStream Reader
目的:OutputStream Writer
2、是否是纯文本?

源:Reader
目的:Writer
3、明确具体设备
源:
硬盘:File
目的;
硬盘:File
FileReader fr=new FileReader(“1.txt”);
FileWriter fw=new FileWriter(“2.txt”);
4、需要额外功能吗?
需要:高效
BufferedReader bufr=new BufferedReader(new FileReader(“1.txt”));
BufferedWriter bufw=new BufferedWriter(new FileWriter(“2.txt”));

需求2:读取键盘录入信息,并写入到一个文件中。

1、明确源和目的。
源:InputStream Reader
目的:OutputStream writer
2、是否是纯文本呢?

源:Reader
目的:Writer
3、明确具体设备
源:
键盘:System.in
目的;
硬盘:File
InputStream in=System.in;
FileWriter fw=new FileWriter(“1.txt”);
4、是否需要额外功能?
是:需要转换
InputStreamWriter isw=new InputStreamWriter(System.in);
FileWriter fw=new FileWriter(“1.txt”);
还需要功能吗?需要高效
BufferedReader bufr=new BufferedReader(new InputStreamReader(System.in))
BufferedWriter bufw=new BufferedWriter(new FileWriter(“1.txt”));

需求3:将一个文本文件数据显示在控制台上。

1、明确源和目的。
源:InputStream Reader
目的:OutputStream writer
2、是否是纯文本呢?

源:Reader
目的:Writer
3、明确具体设备
源:
硬盘:File
目的;
显示器:System.out
FileReader fr=new FileReaderr(“1.txt”);
OutputStream os=System.out;
4、需要额外功能吗?
需要,转换
FileReader fr=new FileReader(“1.txt”);
OutputStreamWriter osw=new OutputStreamWriter(System.out);
需要,高效
BufferedReader bufr=new BufferedReader(new FileReader(“1.txt”));
BufferedWriter bufw=new BufferedWriter(new OutputStreamWriter(System.out));

需求4:读取键盘录入数据,显示在控制台上。

1、明确源和目的。
源:InputStream Reader
目的:OutputStream writer
2、是否是纯文本呢?

源:Reader
目的:Writer
3、明确具体设备
源:
键盘:System.in
目的;
显示器:System.out
InputStream in=System.in;
OutputStream out=System.out;
4、明确额外功能
需要转换:
InputStreamReader isr=new InputStreamReader(System.in);
OutputStreamWriter osw=new OutputStreamWriter(System.out);
为了将其高效。
BufferedReader bufr=new BufferedReader(new InputStreamReaer(System.in));
BufferedWriter bufw=new BufferedWriter(new OutputStreamWriter(System.out));

需求5:将一个中文字符串按照指定的编码表写入到一个文本文件

1、明确源和目的。
源:无
目的:OutputStream writer
2、是否是纯文本呢?

源:无
目的:Writer
3、明确具体设备
源:

目的;
硬盘:File
FileWriter fw=new FileWriter(“1.txt”);
4、需要额外功能吗?
需要转换
注意:既然需求明确了制定编码表的动作那就不可以使用FileWriter,因为FileWriter内部是使用默认的本地码表。只能使用其父类OutputStreamWriter,OutputStreamWriter接受一个字节输出流对象,既然是操作文件,那么应该是FileOutputStream
OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream(“1.txt”),charset);
需要高效吗?
需要
BufferedWriter bufw=new BufferedWriter(new OutputStreamWriter(new FileOutputStream(“1.txt”),charset))

Java IO所采用的模型

还需要详解一下
Java的IO模型设计非常优秀,它使用Decorator(装饰者)模式,按功能划分Stream,您可以动态装配这些Stream,以便获得您需要的功能。
例如,您需要一个具有缓冲的文件输入流,则应当组合使用FileInputStream和BufferedInputStream。

参考资料:
http://blog.csdn.net/tanqian351/article/details/51209438
https://www.2cto.com/kf/201312/262036.html
http://blog.csdn.net/jiangwei0910410003/article/details/22376895
http://blog.csdn.net/u013087513/article/details/52148934
http://bbs.csdn.net/topics/390517474/
http://blog.csdn.net/u013905744/article/details/51924258
毕向东Java讲义 PDF版

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/147000
推荐阅读
相关标签
  

闽ICP备14008679号