Java学习之IO流一

01. IO技术概述

* a: Output
* 把内存中的数据存储到持久化设备上这个动作称为输出(写)Output操作
* b: Input
* 把持久设备上的数据读取到内存中的这个动作称为输入(读)Input操作
* c: IO操作
* 把上面的这种输入和输出动作称为IO操作

02. File类的概述和作用
* A:File类的概述和作用
* a: File的概念
* File类是文件和目录路径名的抽象表示形式
* Java中把文件或者目录(文件夹)都封装成File对象
* 我们要去操作硬盘上的文件,或者文件夹只要找到File这个类即可

03. File类静态的成员变量
* A:File类静态的成员变量
* a: pathSeparator
* 与系统有关的路径分隔符,为了方便,它被表示为一个字符串
* b: separator
* 与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串

* c: 案例代码

/*
 *  java.io.File
 *    将操作系统中的,文件,目录(文件夹),路径,封装成File对象
 *    提供方法,操作系统中的内容
 *    File与系统无关的类
 *    文件 file
 *    目录 directory
 *    路径 path
 */
public class FileDemo {
    public static void main(String[] args) {
        //File类静态成员变量
        //与系统有关的路径分隔符
        String separator = File.pathSeparator;
        System.out.println(separator);// 是一个分号,目录的分割(window中环境变量配置各个路径用分号分割,表示一个完整的路径结束)  Linux中是冒号 :
                    
        //与系统有关的默认名称分隔符
        separator = File.separator;
           System.out.println(separator);// 向右 \  目录名称分割  Linux / 
    }
}

 

 

04. File类构造方法_1
* A: File类构造方法_1
* a: File(String pathname)
* 通过将给定路径名字符串转换为一个File对象,之后可以使用File中的方法
* windows中的路径或文件名不区分大小写
* d: 案例代码

public class FileDemo1 {
    public static void main(String[] args) {
        function();
    }
        /*
     *  File(String pathname)
     *  传递路径名: 可以写到文件夹,可以写到一个文件
     *  c:\\abc   c:\\abc\\Demo.java
     *  将路径封装File类型对象
     */
    public static void function(){
        File file = new File("d:\\eclipse");
        System.out.println(file);
    }   
                    
}

 

05. 相对路径和绝对路径
* A: 相对路径和绝对路径
* a: 绝对路径
* 绝对路径是一个固定的路径,从盘符开始
* b: 相对路径
* 相对路径相对于某个位置,在eclipse下是指当前项目下
* c: 路径
绝对路径
在系统中具有唯一性
c:\\windows\\system32
相对路径
表示路径之间的关系
D:\\develop\\Java\\jdk1.7.0_72\\bin
D:\\develop\\Java\\jre7
路径之间关系
Java 父目录是D:\\develop
Java 子目录是:jdk1.7.0_72
父路径是 唯一性
子目录是可以多个



06. File类的构造方法_2
* A: File类的构造方法_2
* a:File(String parent, String child)
* 根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 对象

* b: File(File parent, String child)

* c: 案例代码

public class FileDemo1 {
    public static void main(String[] args) {
        function_2();
    }
    /*
    * File(File parent,String child)
    * 传递路径,传递File类型父路径,字符串子路径
    * 好处: 父路径是File类型,父路径可以直接调用File类方法
    */
    public static void function_2(){
        File parent = new File("d:");
        File file = new File(parent,"eclipse");
        System.out.println(file);
    }

    /*
    * File(String parent,String child)
    * 传递路径,传递字符串父路径,字符串子路径
    * 好处: 单独操作父路径和子路径
    */
    public static void function_1(){
        File file = new File("d:","eclipse");
        System.out.println(file);
    }
}

###07File类创建文件功能
* A: File类创建文件功能
* a: public boolean createNewFile()
* 创建文件 如果存在这样的文件,就不创建了

* b: 案例代码

public class FileDemo2 {
    public static void main(String[] args)throws IOException {
    function();
    }    
    /*
    * File创建文件的功能
    * boolean createNewFile()
    * 创建的文件路径和文件名,在File构造方法中给出
    * 文件已经存在了,不在创建
    */
    public static void function()throws IOException{
        File file = new File("c:\\a.txt");
        boolean b = file.createNewFile();
        System.out.println(b);
    }
}

 


###08File类创建目录功能
* A: File类创建目录功能
* a: 创建目录
* public boolean mkdir():创建文件夹 如果存在这样的文件夹,就不创建了
* public boolean mkdirs():创建文件夹,如果父文件夹不存在,会帮你创建出来
* b: 案例代码

public class FileDemo2 {
    public static void main(String[] args)throws IOException {
        function_1();
    }
    /*
    * File创建文件夹功能
    * boolean mkdirs() 创建多层文件夹
    * 创建的路径也在File构造方法中给出
    * 文件夹已经存在了,不在创建
    */
    public static void function_1(){
        File file = new File("c:\\abc");
        boolean b = file.mkdirs();
        System.out.println(b);
    }    
}

###09File类删除功能
* A: File类删除功能
* a: 删除功能
* public boolean delete():删除文件或者文件夹
* B: 案例代码

public class FileDemo2 {
    public static void main(String[] args)throws IOException {
        function_2();
    }
    /*
    * File类的删除功能
    * boolean delete()
    * 删除的文件或者是文件夹,在File构造方法中给出
    * 删除成功返回true,删除失败返回false
    * 删除方法,不走回收站,直接从硬盘中删除
    * 删除有风险,运行需谨慎
    */
    public static void function_2(){
        File file = new File("c:\\a.txt");
        boolean b = file.delete();
        System.out.println(b);
    }    
}

###10File类获取功能
* A:File类获取功能
* a: 方法介绍
* String getName(): 返回路径中表示的文件或者文件夹名
* 获取路径中的最后部分的名字
* long length(): 返回路径中表示的文件的字节数
* String getAbsolutePath(): 获取绝对路径,返回String对象
* File getAbsoluteFile() : 获取绝对路径,返回File对象
* eclipse环境中,写一个相对路径,绝对位置工程根目录
* String getParent(): 获取父路径,返回String对象
* File getParentFile(): 获取父路径,返回File对象

* b: 案例代码

public class FileDemo3 {
public static void main(String[] args) {
function_3();
}
/*
* File类的获取功能
* String getParent() 返回String对象
* File getParentFile()返回File对象
* 获取父路径
*/
public static void function_3(){
File file = new File("d:\\eclipse\\eclipse.exe");
File parent = file.getParentFile();
System.out.println(parent);
}

/*
* File类获取功能
* String getAbsolutePath() 返回String对象
* File getAbsoluteFile() 返回File对象
* 获取绝对路径
* eclipse环境中,写的是一个相对路径,绝对位置工程根目录
*/
public static void function_2(){
File file = new File("src");
File absolute = file.getAbsoluteFile();
System.out.println(absolute);
}

/*
* File类获取功能
* long length()
* 返回路径中表示的文件的字节数
*/
public static void function_1(){
File file = new File("d:\\eclipse\\eclipse.exe");
long length = file.length();
System.out.println(length);
}

/*
* File类的获取功能
* String getName()
* 返回路径中表示的文件或者文件夹名
* 获取路径中的最后部分的名字
*/
public static void function(){
File file = new File("d:\\eclipse\\eclipse.exe");
String name = file.getName();
System.out.println(name);

/*String path = file.getPath();
System.out.println(path);*/
// System.out.println(file);
}
}


###11File类判断功能
* A: File类判断功能
* a: 方法介绍
* boolean exists(): 判断File构造方法中封装路径是否存在
* 存在返回true,不存在返回false
* boolean isDirectory(): 判断File构造方法中封装的路径是不是文件夹
* 如果是文件夹,返回true,不是文件返回false
* boolean isFile(): 判断File构造方法中封装的路径是不是文件
* 如果是文件,返回true,不是文件返回false

* b: 案例代码
public class FileDemo4 {
public static void main(String[] args) {
function_1();
}
/*
* File判断功能
* boolean isDirectory()
* 判断File构造方法中封装的路径是不是文件夹
* 如果是文件夹,返回true,不是文件返回false
*
* boolean isFile()
* 判断File构造方法中封装的路径是不是文件
*/
public static void function_1(){
File file = new File("d:\\eclipse\\eclipse.exe");
if(file.exists()){
boolean b = file.isDirectory();
System.out.println(b);
}
}

/*
* File判断功能
* boolean exists()
* 判断File构造方法中封装路径是否存在
* 存在返回true,不存在返回false
*/
public static void function(){
File file = new File("src");
boolean b = file.exists();
System.out.println(b);
}
}


###12File类list获取功能
* A: File类list获取功能
* a: 方法介绍
* String[] list():获取到File构造方法中封装的路径中的文件和文件夹名 (遍历一个目录)
* 返回只有名字
* File[] listFiles():获取到,File构造方法中封装的路径中的文件和文件夹名 (遍历一个目录)
* 返回的是目录或者文件的全路径
* static File[] listRoots(): 列出可用的文件系统根

* b: 案例代码
public class FileDemo {
public static void main(String[] args) {
function_2();
}
public static void function_2(){
//获取系统中的所有根目录
File[] fileArr = File.listRoots();
for(File f : fileArr){
System.out.println(f);
}
}

/*
* File类的获取功能
* File[] listFiles()
* 获取到,File构造方法中封装的路径中的文件和文件夹名 (遍历一个目录)
* 返回的是目录或者文件的全路径
*/
public static void function_1(){
File file = new File("d:\\eclipse");
File[] fileArr = file.listFiles();
for(File f : fileArr){
System.out.println(f);
}
}

/*
* File类的获取功能
* String[] list()
* 获取到,File构造方法中封装的路径中的文件和文件夹名 (遍历一个目录)
* 返回只有名字
*/
public static void function(){
File file = new File("c:");
String[] strArr = file.list();
System.out.println(strArr.length);
for(String str : strArr){
System.out.println(str);
}
}
}

###13文件过滤器
* A: 文件过滤器
* a: 作用
* 过滤一个目录下的指定扩展名的文件,或者包含某些关键字的文件夹

* b: 方法介绍
* public String[] list(FilenameFilter filter)
* public File[] listFiles(FileFilter filter)

* C: 案例代码
/*
* 自定义过滤器
* 实现FileFilter接口,重写抽象方法
*/
public class MyFilter implements FileFilter{
public boolean accept(File pathname) {
/*
* pathname 接受到的也是文件的全路径
* c:\\demo\\1.txt
* 对路径进行判断,如果是java文件,返回true,不是java文件,返回false
* 文件的后缀结尾是.java
*/
//String name = pathname.getName();
return pathname.getName().endsWith(".java");

}
}

/*
* File类的获取,文件获取过滤器
* 遍历目录的时候,可以根据需要,只获取满足条件的文件
* 遍历目录方法 listFiles()重载形式
* listFiles(FileFilter filter)接口类型
* 传递FileFilter接口的实现类
* 自定义FileFilter接口实现类,重写抽象方法,
* 接口实现类对象传递到遍历方法listFiles
*/
public class FileDemo1 {
public static void main(String[] args) {
File file = new File("c:\\demo");
File[] fileArr = file.listFiles(new MyFilter());
for(File f : fileArr){
System.out.println(f);
}
}
}

###14文件过滤器_原理分析
* A:文件过滤器_原理分析
* listFiles()遍历目录的同时,获取到了文件名全路径,调用过滤器的方法accept,将获取到的路径传递给accept方法的参数pathname
* accept方法接收了参数pathname,参数是listFiles传递来的
* 在accept方法中,进行判断,如果这个路径是Java文件,返回true,走着返回false
* 一旦方法返回了true
* listFiles将路径保存到File数组中

###15递归遍历全目录
* A: 递归遍历全目录
* a: 案例代码

/*
* 对一个目录的下的所有内容,进行完全的遍历
* 编程技巧,方法的递归调用,自己调用自己
*/
public class FileDemo {
    public static void main(String[] args) {
    File dir = new File("d:\\eclipse");
    getAllDir(dir);
    }
    /*
     * 定义方法,实现目录的全遍历
     */
     public static void getAllDir(File dir){
        System.out.println(dir);
         //调用方法listFiles()对目录,dir进行遍历
        File[] fileArr = dir.listFiles();
        for(File f : fileArr){
        //判断变量f表示的路径是不是文件夹
        if(f.isDirectory()){
        //是一个目录,就要去遍历这个目录
        //本方法,getAllDir,就是给个目录去遍历
        //继续调用getAllDir,传递他目录
            getAllDir(f);
        }else{
            System.out.println(f);
    }
}
}
}

 

 

 

###16递归概念和注意事项
* A:递归概念和注意事项
* a: 递归概念
* 递归,指在当前方法内调用自己的这种现象
* 递归分为两种,直接递归和间接递归
* 直接递归称为方法自身调用自己。间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法
* b: 注意事项
* 递归一定要有出口, 必须可以让程序停下
* 递归次数不能过多
* 构造方法,禁止递归

###17递归求和计算
* A: 递归求和计算
* a: 题目分析
* 1+2+3+...+(n-1)+n:求1到n的和
* 总结规律:1到n的和等于1到(n-1)的和再加n
* getSum(n-1)+ n
* 递归出口:getSum(1) return 1;

* b: 案例代码
/*
* 方法的递归调用
* 方法自己调用自己
* 适合于,方法中运算的主体不变,但是运行的时候,参与运行的方法参数会变化
* 注意:
* 递归一定要有出口, 必须可以让程序停下
* 递归次数不能过多
* 构造方法,禁止递归
*/
public class DiGuiDemo {
public static void main(String[] args) {
int sum = getSum(3);
System.out.println(sum);
}


/*
* 计算 1+2+3+100和 = 5050
* 计算规律:
* n+(n-1)+(n-2)
* 100+(100-1)+(99-1)+...1
*/
public static int getSum(int n){
if( n == 1)
return 1;
return n + getSum(n-1);
}

}

###18递归求阶乘
* A: 递归求和计算
* a: 题目分析
* 5!=5*4*3*2*1
* =5*4!
* 4!=4*3!
* 3!=3*2!
* 2!=2*1!
* 1!=1
* n!=n*(n-1)!
* 递归出口:n*getJieCheng(n-1): getJieCheng(1) return 1;
* b: 案例代码
/*
* 方法的递归调用
* 方法自己调用自己
* 适合于,方法中运算的主体不变,但是运行的时候,参与运行的方法参数会变化
* 注意:
* 递归一定要有出口, 必须可以让程序停下
* 递归次数不能过多
* 构造方法,禁止递归
*/
public class DiGuiDemo {
public static void main(String[] args) {
System.out.println(getJieCheng(5));

}

/*
* 计算阶乘 5!
* 5*4*3*2*1
*/
public static int getJieCheng(int n){
if ( n == 1)
return 1;
return n * getJieCheng(n-1);
}
}
###19递归计算斐波那契数列
* A: 递归计算斐波那契数列
* a:题目分析
* 1 1 2 3 5 8 13 21
* 从第三项开始,后面的每一项都等于前面两项的和,第一项和第二项的值为1,作为程序的出口
* b: 案例代码

/*
* 方法的递归调用
* 方法自己调用自己
* 适合于,方法中运算的主体不变,但是运行的时候,参与运行的方法参数会变化
* 注意:
* 递归一定要有出口, 必须可以让程序停下
* 递归次数不能过多
* 构造方法,禁止递归
*/
public class DiGuiDemo {
    public static void main(String[] args) {    
    System.out.println(getFBNQ(12));
    }
    /*
    * 方法递归,计算斐波那契数列
    * 
    */
    public static int getFBNQ(int month){
    if( month == 1)
        return 1;
    if(month == 2)
        return 1;
    return getFBNQ(month-1)+getFBNQ(month-2);
    }
}

 


###20遍历目录下的所有java文件
* A: 遍历目录下的所有java文件
* a: 案例代码

 1 /**
 2  * 需求:打印指定目录即所有子目录中的.java文件的文件路径
 3     要求:编写一个方法用来打印指定目录中的.java文件路径,并进行方法的调用
 4     若指定的目录有子目录,那么把子目录中的.java文件路径也打印出来
 5     步骤:
 6     1. 指定要打印的目录File对象
 7     2. 调用getFileAll()方法,传入要打印的目录File对象
 8         2.1 通过FilenameFilter过滤器获取指定目录中的所有.java类型的File对象
 9         2.2 遍历得到每一个File对象
10         2.3 判断当前File 对象是否是目录
11             判断结果为true,说明为目录,通过递归,再次调用步骤2的getFileAll()方法
12             判断结果为false,说明是文件,打印文件的路径
13 
14  * @author vanguard
15  *
16  */
17 public class FileDemo02 {
18     public static void main(String[] args) {
19         File file = new File("e:\\Java\\Java\\day01");
20         getFileAll(file);
21     }
22 
23     private static void getFileAll(File file) {
24         //通过FilenameFilter过滤器获取指定目录中的所有.java类型的File对象
25         File[] files = file.listFiles(new MyFilenameFilter());
26         //遍历得到每一个File对象
27         for(File f : files) {
28             //判断当前File 对象是否是目录
29             if(f.isDirectory()) {
30                 getFileAll(f);
31             } else {
32                 System.out.println(f);
33             }
34         }
35     }
36     
37 }

作业:

1.使用文件名称过滤器筛选将指定文件夹下的小于200K的小文件获取并打印。

 1 /**
 2  * 1.使用文件名称过滤器筛选将指定文件夹下的小于200K的小文件获取并打印。
 3  * @author vanguard
 4  *
 5  */
 6 public class Demo01 {
 7     public static void main(String[] args) {
 8         File file = new File("e:\\Java\\Java");
 9         getFile(file);
10     }
11     public static void getFile(File file) {
12         ////通过FilenameFilter过滤器获取指定目录中小于200K的小文件
13         File[] files = file.listFiles(new FilenameFilter() {
14 
15             @Override
16             public boolean accept(File dir, String name) {
17                 if(new File(dir, name).isDirectory())
18                     return true;
19                 return new File(dir, name).length()/1024 < 200;
20             }
21             
22         });
23         for(File f : files) {
24             if(f.isDirectory()) {
25                 getFile(f);
26             } else {
27                 System.out.println(f);
28             }
29         }
30     }
31 }

2.从键盘接收一个文件夹路径,统计该文件夹大小。

 1 /**
 2  * 2.从键盘接收一个文件夹路径,统计该文件夹大小。
 3  * @author vanguard
 4  *
 5  */
 6 public class Demo02 {
 7     static long size = 0;
 8     public static void main(String[] args) {
 9         File file = new File("e:\\HTML");
10         long size = getDirSize(file);
11         System.out.println(size/1024/1024);
12     }
13     
14     /**
15      * 递归统计文件夹中文件的大小
16      * @param file
17      * @return
18      */
19     private static long getDirSize(File file) {
20         
21         File[] files = file.listFiles();
22         //遍历目录中的文件和文件夹
23         for(File f : files) {
24             if(f.isDirectory()) {
25                 size = getDirSize(f);
26             } else {
27                 size += f.length();
28             }
29         }
30         return size;
31     }
32 }

3.从键盘接收一个文件夹路径,删除该文件夹。

 1 /**
 2  * 3.从键盘接收一个文件夹路径,删除该文件夹。
 3  * @author vanguard
 4  *
 5  */
 6 public class Demo03 {
 7     public static void main(String[] args) {
 8         File dir = new File("e:\\test");
 9         boolean b = deleteDir(dir);
10         if(b) {
11             System.out.println("删除成功");
12         } else {
13             System.out.println("删除失败");
14         }
15     }
16     /**
17      * 递归删除文件夹,包括文件夹中的文件夹和文件
18      * @param file
19      */
20     private static boolean deleteDir(File dir) {
21         File[] files = dir.listFiles();
22         //遍历目录下所有的文件和目录
23         for(File f : files) {
24             //如果是目录,在调用自己删除目录下目录和文件
25             if(f.isDirectory()) {
26                 //删除目录下文件和目录
27                 deleteDir(f);
28                 //删除目录
29                 f.delete();
30             } else {
31                 //如果是文件,删除文件
32                 f.delete();
33             }
34         }
35         //最后删除接收的文件夹
36         return dir.delete();
37     }
38 }

4.从键盘接收一个文件夹路径,把文件夹中的所有文件以及文件夹的名字按层级打印
例如:
aaa是文件夹,里面有bbb.txt,ccc.txt,ddd.txt这些文件,有eee这样的文件夹,eee中有fff.txt和ggg.txt,打印出层级来
aaa
bbb.txt
ccc.txt
ddd.txt
eee
fff.txt
ggg.txt

 1 /**
 2  * 4.从键盘接收一个文件夹路径,把文件夹中的所有文件以及文件夹的名字按层级打印
 3     例如:
 4     aaa是文件夹,里面有bbb.txt,ccc.txt,ddd.txt这些文件,有eee这样的文件夹,eee中有fff.txt和ggg.txt,打印出层级来
 5     aaa
 6         bbb.txt
 7         ccc.txt
 8         ddd.txt    
 9         eee
10             fff.txt
11             ggg.txt
12  * @author vanguard
13  *
14  */
15 public class Demo04 {
16     public static void main(String[] args) {
17         //键盘输入一个文件夹路径
18         String pathname = new Scanner(System.in).next();
19         File src = new File(pathname);
20         System.out.println(src.getName());
21         //打印目录
22         printDir(src, 0);
23     }
24     
25     /**
26      * 递归打印目录
27      * @param src
28      * @param level
29      */
30     private static void printDir(File src, int level) {
31         //划分层级,level代表第几级
32         String pause = "   ";
33         for(int i = 1; i < level; i++) {
34             pause += "   ";
35         }
36         //遍历目录下的文件及文件夹
37         for(File f : src.listFiles()) {
38             System.out.println(pause + f.getName());
39             //如果是文件夹,调用打印文件夹方法
40             if(f.isDirectory()) {
41                 printDir(f, level + 1);
42             }
43         }
44     }
45 }

5.键盘录入一个文件夹路径,统计该文件夹(包含子文件夹)中每种类型的文件及个数,注意:用文件类型(后缀名,不包含.(点),如:"java","txt")作为key,
用个数作为value,放入到map集合中,并用两种方式遍历map集合
例如:
doc 的类型的文件有 3 个
java 的类型的文件有 5 个
txt 的类型的文件有 7 个

 

 1 /**
 2  * 5.键盘录入一个文件夹路径,统计该文件夹(包含子文件夹)中每种类型的文件及个数,注意:用文件类型(后缀名,不包含.(点),如:"java","txt")作为key,
 3     用个数作为value,放入到map集合中,并用两种方式遍历map集合
 4     例如:
 5     doc 的类型的文件有  3 个
 6     java 的类型的文件有  5 个
 7     txt 的类型的文件有  7 个
 8  * @author vanguard
 9  *
10  */
11 public class Demo05 {
12     public static void main(String[] args) {
13         //文件输入一个文件夹路径
14         String path = new Scanner(System.in).next();
15         File parent = new File(path);
16         //定义存放每种类型的文件及个数,类型作为键,个数作为值
17         Map<String, Integer> files = new HashMap<String, Integer>();
18         //统计各个类型文件的个数
19         fileNum(parent, files);
20         //遍历Map集合中各个文件类型的个数
21         printNum(files);
22     }
23     
24     /**
25      * 用两种方式遍历Map集合
26      * @param files
27      */
28     private static void printNum(Map<String, Integer> files) {
29         //利用键找值的方式遍历Map集合
30         for(String type : files.keySet()) {
31             Integer num = files.get(type);
32             System.out.println(type + " 的类型的文件有" + num + "个");
33         }
34         System.out.println("=================");
35         for(Map.Entry<String, Integer> entry : files.entrySet()) {
36             String type = entry.getKey();
37             Integer num = entry.getValue();
38             System.out.println(type + " 的类型的文件有" + num + "个");
39         }
40     }
41     /**
42      * 统计文件夹中每种类型文件的个数
43      * @param parent
44      * @param files
45      */
46     private static void fileNum(File parent, Map<String, Integer> files) {
47         
48         File[] listFiles = parent.listFiles();
49         //遍历目录下的文件夹及文件
50         for(File f : listFiles) {
51             //如果遍历的是文件夹,调用统计文件夹中每种类型文件个数的方法
52             if(f.isDirectory()) {
53                 fileNum(f, files);
54             } else {
55                 //如果是文件,截取文件后缀
56                 String[] ch = f.getName().split("\\.");
57                 //获取文件类型后缀
58                 String type = ch[ch.length - 1];
59                 //统计各个类型文件的个数
60                 if(!files.containsKey(type)) {
61                     files.put(type, 1);
62                 } else {
63                     files.put(type, files.get(type) + 1);
64                 }
65             }
66         }
67     }
68 }

 

posted @ 2017-07-21 21:21  Vanguard  阅读(710)  评论(0编辑  收藏  举报