IO流与File类

IO流

Io流大纲

原理:

  1. IO流用来处理设备之间的数据传输。

  2. Java程序中,对于数据的输入/输出操作以”流(stream)” 的方式进行。是指从源节点到目标节点的数据流动

  3. 源节点和目标节点可以是文件、网络、内存、键盘、显示器等等。

  4. java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。

  5. 输入input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。

  6. 输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中

重点总结:

***IO原理及流的分类
***文件流
FileReader / FileWriter / FileInputStream / FileOutputStream  
***缓冲流
BufferedReader / BufferedWriter
BufferedInputStream / BufferedOutputStream
**对象流   ----涉及序列化、反序列化
ObjectInputStream / ObjectOutputStream
数据流(了解)
DataInputStream / DataOutputStream
**随机存取文件流(了解)
RandomAccessFile
**转换流(明白)
InputStreamReader / OutputStreamWriter
标准输入/输出流
打印流(了解)
PrintStream / PrintWriter
***java.io.File类的使用

A

序列化 : 把对象在GC区中的数据写入文件 反序列化 : 把文件中的数据还原成对象 对象要想被序列化, 它的类必须要实现Serializable接口 处理流也称为高级流或包装流. 二进制文件中的数据特点 : 数据在内存中怎么保存, 数据在文件中也怎么保存.

分类

数据流的方向:

输入流:获取资源到程序中

输出流:资源从程序输出到别的地方

处理数据单位:

1字符 = 2字节 、 1字节(byte) = 8位(bit) 、 一个汉字占两个字节长度

字节流:每次存储一个字节,有中文时,就会出现乱码。

字符流:每次存储两个个字节,有中文时,使用该流就可以正确传输显示中文。

功能:

节点流:从或向一个特定的地方(节点)读写数据。如FileInputStream 

处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader。处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,

基本抽象流类型:

 字节流字符流
输入流 InputStream(基类) Reader(基类)
输出流 OutputStream (基类) Writer(基类)
流中的数据 二进制字节(8位) Unicode字符(16位)
输入流:
Reader 和 InputStream 是所有输入流的基类。
Reader(典型实现:FileReader)
int read() // 读取一个字符
int read(char [] c) //一次性读多个字符到缓冲区数组
int read(char [] c, int off, int len)
InputStream(典型实现:FileInputStream)
int read() //读取一个字节
int read(byte[] b) //一次性读多个字节到缓冲区数组
int read(byte[] b, int off, int len)
输出流
Writer 和 OutputStream 也非常相似:
void write(int b/int c);
void write(byte[] b/char[] cbuf);
void write(byte[] b/char[] buff, int offset, int length);
void flush();
void close(); 需要先刷新,再关闭此流
因为字符流直接以字符作为操作单位,所以 Writer 可以用字符串来替换字符数组,即以 String 对象作为参数
void write(String str);
void write(String str, int off, int len);

 

节点流

字节文件输入流 : FileInputStream 字节文件输出流 : FileOutputStream

文本文件输入流 : FileReader 文本文件输出流 : FileWriter

输入流的处理方法

  • int read(); // 从输入流中读一个字符或字节数据

  • int read(char[] arr) //一次性读多个字符到数组中, 返回值就是实际读的字符数

输出流的处理方法

  • void write(int data) // 把参数中的一个数据写到输出流

  • void write(char[] buf) // 把参数中的数组中所有数据全部写到输出流, 此方法几乎不用

  • void write(char[] buf, int offset, int length) // 把参数中的数组中一部分数据全部写到输出流, 从offset下标开始, 总共写length个字符

读写文件流程

基础版:

仅仅是一个一个的进行读与写速度太慢了因为读取都是一个字符循环的次数太多了

    // 读一个文本文件
       // 1) 声明引用并赋值为null
       FileReader fileReader = null;
       // 2) try catch finally
       try {
           // 5 在try中创建对象
           fileReader = new FileReader("文本文件");
           // 6 处理数据
           int ch;
           while ((ch = fileReader.read()) != -1) {
               // 真的处理已经读到的数据
               System.out.print((char)ch);
          }
      } catch (IOException e) {// 4) 在catch中处理异常
           e.printStackTrace();
      } finally {// 3) 在finally中关闭流
           if (fileReader != null) {
               try {fileReader.close();}
               catch (IOException ioException) {ioException.printStackTrace();}
          }
      }
       
       // 写一个文本文件
       FileWriter fileWriter = null;
       try {
           fileWriter = new FileWriter("写一个文件");
           fileWriter.write('我');
           fileWriter.write('和');
           fileWriter.write('你');
           fileWriter.write(10);//换行

           fileWriter.write('13');
           fileWriter.write('14');
           fileWriter.write('\r'); // windows系统要求的换行必须要有\r
           fileWriter.write('\n'); //换行

           fileWriter.write('y');
           fileWriter.write('e');
           fileWriter.write('\r');//回车的操作
           fileWriter.write(10);

      } catch (Exception e) {
           e.printStackTrace();
      } finally {
           if (fileWriter != null) {
               try {
                   fileWriter.close();
              } catch (IOException e) {
                   e.printStackTrace();
              }
          }
      }
基础加强版:

运用缓存的概念,先将一个个字符存储到一个字符数组中,然后循环就可以根据缓存的次数进行存储

//读文件
fileReader = new FileReader("文本文件");
   char[] buf = new char[100]; // 用于缓冲100个字符的数组
   int n; // n是实际读入数组的字符数, n是<=100
   while ((n = fileReader.read(buf)) != -1) { // 把n个字符读入到数组中
       // 处理已经读到的数据, 已经读的是n个字符
       for (int i = 0; i < n; i++) { // 不要循环数组长度次 如果按数组长度会出现脏读
           System.out.print(buf[i]);
      }
  }
//写文件
   fileWriter = new FileWriter("使用缓冲写文件");
   String[] content = {"2398472342o34jlasjdflkajsdflk",
                       "夺回蓝sdlkjasdlfkja sldkfjalskdjf",
                       "我是字符串,你来写我呀1",
                       "我是字符串,你来写我呀2",
                       "我是字符串,你来写我呀3"};
   for (int i = 0; i < content.length; i++) {
       char[] chars = content[i].toCharArray();
       //fileWriter.write(chars);
       fileWriter.write(chars, 1, chars.length - 1); // 第2个参数是数组的开始下标, 第3个参数是要写字符数
       fileWriter.write(13);
       fileWriter.write(10);
}

 

文件的复制 = 读文件 + 写文件

通过字符流来进行复制

字符流的只能进行文本文件的复制,但是如果文件的底层是两个字节的文件,例如视频之类的则会使复制的文件不能使用

public static void main(String[] args) {
       FileReader fileReader = null;
       FileWriter fileWriter = null;
       try {
           //fileReader = new FileReader("./src/com/atguigu/javase/io/FileCopy.java");
           fileReader = new FileReader("lesson.mp4");
           fileWriter = new FileWriter("上课.mp4");
           char[] buf = new char[8192];
           int n;
           while ((n = fileReader.read(buf)) != -1) { // 以读为主导
               // 处理已经读的数据
               // 把读到的数据原封不动地写入到输出流中
               fileWriter.write(buf, 0, n); // 写入数组的一部分
          }
      } catch (Exception e) {
           e.printStackTrace();
      } finally {
           // 分开独立关闭
           if (fileReader != null) {
               try {
                   fileReader.close();
              } catch (IOException e) {
                   e.printStackTrace();
              }
          }

           if (fileWriter != null) {
               try {
                   fileWriter.close();
              } catch (IOException e) {
                   e.printStackTrace();
              }
          }
      }
  }
字节流复制:最靠谱的文件复制的方式

比字符流复制更靠谱。

FileInputStream fis = null;
FileOutputStream fos = null;
try {
   fis = new FileInputStream("文本文件");
   fos = new FileOutputStream("文件文件222");
   byte[] buf = new byte[8192]; // 8k缓冲区是最好的.
   int n;
   while ((n = fis.read(buf)) != -1) {
       // 处理已经读的数据
       fos.write(buf, 0, n);
  }
} catch (IOException e) {
   e.printStackTrace();
} finally {
   if (fis != null) {
       try {
           fis.close();
      } catch (IOException e) {
           e.printStackTrace();
      }
  }
   if (fos != null) {
       try {
           fos.close();
      } catch (IOException e) {
           e.printStackTrace();
      }
  }
}

处理流:将基础流进行包装

处理流是“连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。

通常很少使用单个流对象,而是将一系列的流以包装的形式链接起来处理数据。

包装可以在不改变被包装流的前提下,获得更强的流处理功能。

 

缓冲流

缓冲字节输入流 : BufferedInputStream 缓冲字节输出流 : BufferedOutputStream

缓冲文本输入流 : BufferedReader 缓冲文本输出流 : BufferedWriter

读写文件流程

读文件
    public void test5() {
       // 使用缓冲流
       FileReader fileReader = null;
       BufferedReader bufReader = null;
       try {
           fileReader = new FileReader("文本文件");
           // 包装, 其实就是对象关联, 对象关联99.9%情况是使用构造器
           bufReader = new BufferedReader(fileReader);
           // 处理数据
           String line;
           // readLine()方法是它的最有价值方法.
           while ((line = bufReader.readLine()) != null) { // 直接从文本文件中读一行字符串
               // 处理数据, 读到的字符串中不包含行结束符
               System.out.println(line);
          }
      } catch (Exception e) {
           e.printStackTrace();
      } finally {
           // 关闭流, 只关闭高级流. 高级流的底层依旧是低级流,而高级流内有关闭低级流的方法
           if (bufReader != null) {
               try {
                   bufReader.close();
              } catch (IOException e) {
                   e.printStackTrace();
              }
          }
      }
  }
写文件
public void test6() {
   FileWriter fileWriter = null;
   BufferedWriter bufWriter = null;
   try {
       fileWriter = new FileWriter("缓冲流写文件");
       bufWriter = new BufferedWriter(fileWriter);
       String[] content = {"aldfjkaklsdjflaksjdflkajsdf",
                           "我是汉字, 我是汉字, 来吧来吧, 写入我吧....",
                           "我是汉字, 我是汉字, 来吧来吧, 写入我吧....",
                           "我是汉字, 我是汉字, 来吧来吧, 写入我吧...."
                          };
       for (int i = 0; i < content.length; i++) {
           bufWriter.write(content[i]);
           bufWriter.newLine(); // 向文本文件中写入跨平台的换行.
           // newLine()方法是最有价值方法, 和 readLine()方法对应
      }
  } catch (Exception e) {
       e.printStackTrace();
  } finally {
       if (bufWriter != null) {
           try {
               bufWriter.close();
          } catch (IOException e) {
               e.printStackTrace();
          }
      }
  }
}

对象流

对象输入流 : ObjectInputStream 对象输出流 : ObjectOutputStream

读写文件

二进制文件处理

写文件
public void test7() {
       FileOutputStream fos = null;
       BufferedOutputStream bos = null;
       ObjectOutputStream oos = null;
  //范围:oos>bos>fos
       try {
           fos = new FileOutputStream("二进制文件");
           bos = new BufferedOutputStream(fos);
           oos = new ObjectOutputStream(bos);

           oos.writeInt(15); // 数据在内存中怎么保存, 就怎么写文件
           oos.writeBoolean(true);
           oos.writeBoolean(false);
           oos.writeLong(20);
           oos.writeDouble(3.14);

           oos.writeUTF("abc我和你yyy"); // 这样写最好, 因为它有长度信息.
           oos.writeChars("abc我和你yyy"); // 这样写字符串不好
      } catch (Exception e) {
           e.printStackTrace();
      } finally {
           if (oos != null) {
               try {
                   oos.close();
              } catch (IOException e) {
                   e.printStackTrace();
              }
          }
      }
  }
读文件
public void test8() {
   FileInputStream fis = null;
   BufferedInputStream bis = null;
   ObjectInputStream ois = null;
   try {
       fis = new FileInputStream("二进制文件");
       bis = new BufferedInputStream(fis);
       ois = new ObjectInputStream(bis);

       int i = ois.readInt();
       System.out.println(i);
       boolean b1 = ois.readBoolean();
       boolean b2 = ois.readBoolean();
       System.out.println(b1);
       System.out.println(b2);
       long l = ois.readLong();
       System.out.println(l);
       double v = ois.readDouble();
       System.out.println(v);
       String s = ois.readUTF();
       System.out.println(s);
  } catch (Exception e) {
       e.printStackTrace();
  } finally {
       if (ois != null) {
           try {
               ois.close();
          } catch (IOException e) {
               e.printStackTrace();
          }
      }
  }
}

转换流

在字节流和字符流之间的转换 (字节流中的数据都是字符时,转成字符流操作更高效。)

InputStreamReader 和 OutputStreamWriter

InputStreamReader

用于将字节流中读取到的字节按指定字符集解码成字符。需要和InputStream“套接”。

构造方法 public InputStreamReader(InputStream in) public InputSreamReader(InputStream in,String charsetName) 如: Reader isr = new InputStreamReader(System.in,”ISO5334_1”);

OutputStreamWriter

用于将要写入到字节流中的字符按指定字符集编码成字节。需要和OutputStream“套接”。 构造方法 public OutputStreamWriter(OutputStream out) public OutputSreamWriter(OutputStream out,String charsetName)

image-20210119200609031

public void testMyInput() throws Exception{
   FileInputStream fis = new FileInputStream("dbcp.txt");
   FileOutputStream fos = new FileOutputStream("dbcp5.txt");

   InputStreamReader isr = new InputStreamReader(fis,"GBK");
   OutputStreamWriter osw = new OutputStreamWriter(fos,"GBK");

   BufferedReader br = new BufferedReader(isr);
   BufferedWriter bw = new BufferedWriter(osw);

   String str = null;
   while((str = br.readLine()) != null){
       bw.write(str);
       bw.newLine();
       bw.flush();
}    bw.close();  br.close();}

练习:

    // 练习 : 使用转换流写一个文本文件, 编码方式是GBK
   // 再写程序读取这个文本文件, 要想正确读取, 必须要用转换流
FileOutputStream fos = null;
       OutputStreamWriter osw = null;
       BufferedWriter bufWriter = null;
       try {
           fos = new FileOutputStream("写指定编码文件", true);
           osw = new OutputStreamWriter(fos, "gbk"); // 写文件时指定编码
           bufWriter = new BufferedWriter(osw);
           bufWriter.write("234234234"); // 9
           bufWriter.newLine(); // 2
           bufWriter.write("我要写汉字了1");
           bufWriter.newLine(); // 2

           bufWriter.flush(); // 冲刷缓冲区, 让缓冲区中的数据写入目标
      } catch (Exception e) {
           e.printStackTrace();
      } finally {
           if (bufWriter != null) {
               try {
                   bufWriter.close(); // 流在关闭时, 会自动调用flush
              } catch (Exception e) {
                   e.printStackTrace();
              }
          }
      }

 

标准输入输出流

System.inSystem.out分别代表了系统标准的输入和输出设备 默认输入设备是键盘,输出设备是显示器 System.in的类型是InputStream System.out的类型是PrintStream,其是OutputStream的子类FilterOutputStream 的子类 通过System类的setInsetOut方法对默认设备进行改变。 public static void setIn(InputStream in) public static void setOut(PrintStream out)

B

* FileReader 太弱, 只能读取项目默认编码的文本文件
* 必须使用转换流InputStreamReader, 把字节流转换为字符流
* 写文本文件时, 如果需要指定编码方式, 也必须要使用转换流
* OutputStreamWriter
*
* 输出流 : 必须关闭或flush, 才能保证缓冲区中的数据真实地写入目标
* 在输出时可以选择以追加的方式写文件
* new FileOutputStream("文件名", true);
*
* PrintStream打印流会自动flush

综合小练习:

// 从键盘读取一些内容, 把这些内容以行的形式写入文本文件keyboard.txt,要求写入成gbk编码.
public static void main(String[] args) {
   //输入流
  //(标准输入流)键盘输入流
       InputStream is = System.in;
  //转换流
       InputStreamReader isr = null ;
  //缓冲流
       BufferedReader bufReader = null;
//输出流
       FileOutputStream fos = null;
       OutputStreamWriter osw = null;
       BufferedWriter bufWriter = null;

       try {
       //输入流
           //进行转换
           isr = new InputStreamReader(is);
           //进行包装
           bufReader = new BufferedReader(isr);
//输出流
           //文件流
           fos = new FileOutputStream("keyboard.txt");
           //转换流 指明编码格式
           osw = new OutputStreamWriter(fos, "gbk");
           //进行包装
           bufWriter = new BufferedWriter(osw);

           //进行读文件
           String line;
           while ((line = bufReader.readLine()) != null) {
               if (line.equals("exit")) {
                   break;
              }
               bufWriter.write(line);
               bufWriter.newLine();
          }
      } catch (Exception e) {
           e.printStackTrace();
      } finally {
           //流的关闭
           if (bufReader != null) {
               try {
                   bufReader.close();
              } catch (IOException e) {
                   e.printStackTrace();
              }
          }
           if (bufWriter != null) {
               try {
                   bufWriter.close();
              } catch (IOException e) {
                   e.printStackTrace();
              }
          }
      }

  }

打印流(了解)

  • 在整个IO包中,打印流是输出信息最方便的类。

  • PrintStream(字节打印流)和PrintWriter(字符打印流)

    • 提供了一系列重载的print和println方法,用于多种数据类型的输出

    • PrintStream和PrintWriter的输出不会抛出异常

    • PrintStream和PrintWriter有自动flush功能

    • System.out返回的是PrintStream的实例

    FileOutputStream fos = null;
    try {
    fos = new FileOutputStream(new File("D:\\IO\\text.txt"));
    } catch (FileNotFoundException e) {
    e.printStackTrace();
    }//创建打印输出流,设置为自动刷新模式(写入换行符或字节 '\n' 时都会刷新输出缓冲区)
    PrintStream ps = new PrintStream(fos,true);
    if (ps != null) { // 把标准输出流(控制台输出)改成文件
    System.setOut(ps);}
    for (int i = 0; i <= 255; i++) { //输出ASCII字符
    System.out.print((char)i);
    if (i % 50 == 0) {   //每50个数据一行
    System.out.println(); // 换行
    } }
    ps.close();

数据流(了解)

  • 为了方便地操作Java语言的基本数据类型的数据,可以使用数据流。

  • 数据流有两个类:(用于读取和写出基本数据类型的数据)

    1. DataInputStream 和 DataOutputStream

    2. 分别“套接”在 InputStream 和 OutputStream 节点流上

  • DataInputStream中的方法

    boolean readBoolean()byte readByte()
    char readChar() float readFloat()
    double readDouble() short readShort()
    long readLong() int readInt()
    String readUTF() void readFully(byte[] b)
  • DataOutputStream中的方法

    将上述的方法的read改为相应的write即可。

DataOutputStream dos = null;
try { //创建连接到指定文件的数据输出流对象
dos = new DataOutputStream(new FileOutputStream(
"d:\\IOTest\\destData.dat"));
dos.writeUTF("ab中国");  //写UTF字符串
dos.writeBoolean(false);  //写入布尔值
dos.writeLong(1234567890L);  //写入长整数
System.out.println("写文件成功!");
} catch (IOException e) {
e.printStackTrace();
} finally { //关闭流对象
try {
if (dos != null) {
// 关闭过滤流时,会自动关闭它包装的底层节点流
dos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}

RandomAccessFile 类(了解)

  • RandomAccessFile 类支持 “随机访问” 的方式,程序可以直接跳到文件的任意地方来读、写文件

    • 支持只访问文件的部分内容

    • 可以向已存在的文件后追加内容

  • RandomAccessFile 对象包含一个记录指针,用以标示当前读写处的位置。RandomAccessFile 类对象可以自由移动记录指针:

    • long getFilePointer()获取文件记录指针的当前位置

    • void seek(long pos):将文件记录指针定位到 pos 位置

  • 构造器

    • public RandomAccessFile(File file, String mode)

    • public RandomAccessFile(String name, String mode)

  • 创建 RandomAccessFile 类实例需要指定一个 mode 参数,该参数指定 RandomAccessFile 的访问模式:

    1. r: 以只读方式打开

    2. rw:打开以便读取和写入

    3. rwd:打开以便读取和写入;同步文件内容的更新

    4. rws:打开以便读取和写入;同步文件内容和元数据的更新

总结

image-20210119195608625

  • 重点掌握 : FileInputStream, FileOutputStream

  • InputStreamReader, OutputStreamWriter

  • BufferedReader, BufferedWriter

字节编码:

编码表的由来

计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识别各个国家的文字。就将各个国家的文字用数字来表示,并一一对应,形成一张表。这就是编码表。

常见的编码表
  • ASCII:美国标准信息交换码。 用一个字节的7位可以表示。

  • ISO8859-1:拉丁码表。欧洲码表 用一个字节的8位表示。

  • GB2312:中国的中文编码表。

  • GBK:中国的中文编码表升级,融合了更多的中文文字符号。

  • Unicode:国际标准码,融合了多种文字。 所有文字都用两个字节来表示,Java语言使用的就是unicode

  • UTF-8:通常用三个字节来表示一个字符。

编码:字符串-->字节数组

byte[] = 字符串.getBytes(“编码方式名”);

解码:字节数组-->字符串

String str = new String(byte[],”编码方式名”);

转换流的编码应用

可以将字符按指定编码格式存储。 可以对文本数据按指定编码格式来解读。 指定编码表的动作由构造器完成。

File类

java.io.File类:文件和目录路径名的抽象表示形式,与平台无关 File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流。 File对象可以作为参数传递给流的构造函数

File类的常见构造方法:

  • public File(String pathname)

    • 以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储。

  • public File(String parent,String child)

    • 以parent为父路径,child为子路径创建File对象。

File的静态属性String separator存储了当前系统的路径分隔符。 在UNIX中,此字段为‘/’,在Windows中,为‘\

方法:

访问文件名:

getName()

getPath()
getAbsoluteFile()
getAbsolutePath()
getParent()

renameTo (File newName)

文件检测

exists()

canWrite()
canRead()
isFile()
isDirectory()

获取常规文件信息

lastModified()
length()

文件操作相关

createNewFile()

delete()

目录操作相关

mkDir()

mkDirs()

list()
listFiles()

综合练习

        File file = new File("写指定编码文件");
       System.out.println("file.canReader() : " + file.canRead());
       System.out.println("file.getAbsolutePath() : " + file.getAbsolutePath());
       System.out.println("file.getName() : " + file.getName());
       System.out.println("file.exists() : " + file.exists());
       System.out.println("file.getFreeSpace() : " + file.getFreeSpace());
       System.out.println("file.getTotalSpace() : " + file.getTotalSpace());
       System.out.println("file.isDirectory() : " + file.isDirectory());
       System.out.println("file.isFile() : " + file.isFile());
       System.out.println("file.lastModified() : " + file.lastModified());
       System.out.println("file.length() : " + file.length());
       try {
           file.createNewFile();// 创建一个新的空文件, 没有内容
      } catch (IOException e) {
           e.printStackTrace();
      }
       //System.out.println("file.delete() : " + file.delete());
    /*
   在当前目录下创建子目录(“aaa/bbb/ccc”)
       1)   在其中创建多个文件和目录
       2)   编写方法,实现删除子目录ccc中文件的操作
       3)   把当前目录(“.”目录)的所有文件名称写入到aaa/bbb/ccc/fileList.txt文件中
   */
 @Test
   public void test6() {
       File file = new File("aaa/bbb/ccc/ddd/eee");
       //file.createNewFile();
       //file.mkdir(); // 创建单层目录
       file.mkdirs(); // 创建多层目录

       File[] files = file.listFiles(); // 列出file目录下的所有内容, 包括子目录和子文件的所有File对象数组
       for (int i = 0; i < files.length; i++) {
           System.out.println(files[i] + "," + files[i].isFile());
      }

       System.out.println(file.length()); // 目录的长度如果是非0, 说明它非空
       // 如果目录的长度为0, 说明这个目录是空目录.
  }

 

posted @ 2021-01-25 13:47  WZZR  阅读(167)  评论(0)    收藏  举报