Java中的IO整理完整版(一)

来源:互联网 发布:xbox one x 知乎 编辑:程序博客网 时间:2024/04/27 18:20



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


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类的两个常量


import java.io.*;  
class hello{  
    public static void main(String[] args) {  
        System.out.println(File.separator);  
        System.out.println(File.pathSeparator);  
    }  

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


现在我们使用File类中的常量改写上面的代码:


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类中的常量。


删除一个文件


/**  
 * 删除一个文件  
 * */ 
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("文件不存在");  
        }  
          
    }  

创建一个文件夹


/**  
 * 创建一个文件夹  
 * */ 
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文件夹


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


/**  
 * 使用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的数组


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


/**  
 * 使用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更加方便、


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


/**  
 * 使用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


搜索指定目录的全部内容


/**  
 * 列出指定目录的全部内容  
 * */ 
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写入文件】


/**  
 * 使用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查看的话,会发现那是乱码。


字节流


【向文件中写入字符串】


/**  
 * 字节流  
 * 向文件中写入字符串  
 * */ 
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会看到“你好”


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


/**  
 * 字节流  
 * 向文件中一个字节一个字节的写入字符串  
 * */ 
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();  
    }  

结果还是:“你好”


向文件中追加新内容:


/**  
 * 字节流  
 * 向文件中追加新内容:  
 * */ 
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


【读取文件内容】


/**  
 * 字节流  
 * 读文件内容  
 * */ 
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);的返回值来设计程序。如下:


/**  
 * 字节流  
 * 读文件内容  
 * */ 
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


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


/**  
 * 字节流  
 * 读文件内容,节省空间  
 * */ 
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


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


/**  
 * 字节流  
 * 读文件内容,节省空间  
 * */ 
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));  
    }  

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


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


/**  
 * 字节流  
 *读文件  
 * */ 
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的


现在我们使用字符流


/**  
 * 字符流  
 * 写入数据  
 * */ 
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的内容就会换行了。


从文件中读内容:


/**  
 * 字符流  
 * 从文件中读出内容  
 * */ 
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


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


/**  
 * 字符流  
 * 从文件中读出内容  
 * */ 
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文件中,那么我们就可以使用下面的命令:


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




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


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


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


首先编写下面的代码:


/**  
 * 文件的复制  
 * */ 
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();   
    }  

然后在命令行下面


javac hello.java


java hello d:\hello.txt d:\rollen.txt


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






OutputStreramWriter 和InputStreamReader类


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


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


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


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


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


/**  
 * 将字节输出流转化为字符输出流  
 * */ 
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


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


/**  
 * 将字节输入流变为字符输入流  
 * */ 
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 主要将内容从内存输出


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


/**  
 * 使用内存操作流将一个大写字母转化为小写字母  
 * */ 
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 管道输入流


验证管道流


/**  
 * 验证管道流  
 * */ 
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


打印流


/**  
 * 使用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


当然也可以格式化输出


/**  
 * 使用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向屏幕上输出内容


/**  
 * 使用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


输入输出重定向


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文件的时候,会在里面看到:这些内容在文件中才能看到哦!


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中会看到:这些在文件中才能看到哦!


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中的内容是:“这些文件中的内容哦!”,然后运行程序,输出的结果为:读入的内容为:这些文件中的内容哦!

文章来自:http://zhaohe162.blog.163.com/blog/static/3821679720118309710914/?suggestedreading&wumii