码迷,mamicode.com
首页 > 编程语言 > 详细

java IO流全面总结

时间:2017-12-09 00:02:37      阅读:285      评论:0      收藏:0      [点我收藏+]

标签:无法   stack   style   构建   12px   otf   写文件   缓冲流   字节流   

流的概念和作用

流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。 

 

Java流操作有关的类或接口:

技术分享图片

Java流类图结构:

技术分享图片

 

java输入/输出流体系中常用的流的分类表

分类字节输入流字节输出流字符输入流字符输出流
抽象基类 InputStream OutputStream Reader Writer
访问文件 FileInputStream FileOutputStream FileReader FileWriter
访问数组 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter
访问管道 PipedInputStream PipedOutputStream PipedReader PipedWriter
访问字符串     StringReader StringWriter
缓冲流 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter
转换流     InputStreamReader OutputStreamWriter
对象流 ObjectInputStream ObjectOutputStream    
抽象基类 FilterInputStream FilterOutputStream FilterReader FilterWriter
打印流   PrintStream   PrintWriter
推回输入流 PushbackInputStream   PushbackReader  
特殊流 DataInputStream DataOutputStream    

 

注:表中粗体字所标出的类代表节点流,必须直接与指定的物理节点关联:红色斜体字标出的类代表抽象基类,无法直接创建实例。

  • FileInputStream类的使用:读取文件内容
 1 package com.app;
 2 
 3 import java.io.FileInputStream;
 4 import java.io.FileNotFoundException;
 5 import java.io.IOException;
 6 
 7 public class A1 {
 8 
 9     public static void main(String[] args) {
10         A1 a1 = new A1();
11     
12         //电脑d盘中的abc.txt 文档
13         String filePath = "D:/abc.txt" ;
14         String reslut = a1.readFile( filePath ) ;
15         System.out.println( reslut ); 
16     }
17 
18 
19     /**
20      * 读取指定文件的内容
21      * @param filePath : 文件的路径
22      * @return  返回的结果
23      */
24     public String readFile( String filePath ){
25         FileInputStream fis=null;
26         String result = "" ;
27         try {
28             // 根据path路径实例化一个输入流的对象
29             fis  = new FileInputStream( filePath );
30 
31             //2. 返回这个输入流中可以被读的剩下的bytes字节的估计值;
32             int size =  fis.available() ;
33             //3. 根据输入流中的字节数创建byte数组;
34             byte[] array = new byte[size];
35             //4.把数据读取到数组中;
36             fis.read( array ) ; 
37 
38             //5.根据获取到的Byte数组新建一个字符串,然后输出;
39             result = new String(array); 
40 
41         } catch (FileNotFoundException e) {
42             e.printStackTrace();
43         }catch (IOException e) {
44             e.printStackTrace();
45         }finally{
46             if ( fis != null) {
47                 try {
48                     fis.close();
49                 } catch (IOException e) {
50                     e.printStackTrace();
51                 }
52             }
53         }
54 
55         return result ;
56     }
57 
58 
59 }

 

  • FileOutputStream 类的使用:将内容写入文件
 1 package com.app;
 2 import java.io.FileNotFoundException;
 3 import java.io.FileOutputStream;
 4 import java.io.IOException;
 5 
 6 public class A2 {
 7 
 8     public static void main(String[] args) {
 9         A2 a2 = new A2();
10 
11         //电脑d盘中的abc.txt 文档
12         String filePath = "D:/abc.txt" ;
13 
14         //要写入的内容
15         String content = "今天是2017/1/9,天气很好" ;
16         a2.writeFile( filePath , content  ) ;
17 
18     }
19 
20     /**
21      * 根据文件路径创建输出流
22      * @param filePath : 文件的路径
23      * @param content : 需要写入的内容
24      */
25     public void writeFile( String filePath , String content ){
26         FileOutputStream fos = null ;
27         try {
28             //1、根据文件路径创建输出流
29             fos  = new FileOutputStream( filePath );
30 
31             //2、把string转换为byte数组;
32             byte[] array = content.getBytes() ;
33             //3、把byte数组输出;
34             fos.write( array );
35 
36         } catch (FileNotFoundException e) {
37             e.printStackTrace();
38         }catch (IOException e) {
39             e.printStackTrace();
40         }finally{
41             if ( fos != null) {
42                 try {
43                     fos.close();
44                 } catch (IOException e) {
45                     e.printStackTrace();
46                 }
47             }
48         }
49     }
50 
51 
52 }

 

注意:

  1. 在实际的项目中,所有的IO操作都应该放到子线程中操作,避免堵住主线程。
  2. FileInputStream在读取文件内容的时候,我们传入文件的路径("D:/abc.txt"), 如果这个路径下的文件不存在,那么在执行readFile()方法时会报FileNotFoundException异常。
  3. FileOutputStream在写入文件的时候,我们传入文件的路径("D:/abc.txt"), 如果这个路径下的文件不存在,那么在执行writeFile()方法时, 会默认给我们创建一个新的文件。还有重要的一点,不会报异常。

 缓冲流

首先抛出一个问题,有了InputStream为什么还要有BufferedInputStream?

BufferedInputStreamBufferedOutputStream这两个类分别是FilterInputStreamFilterOutputStream的子类,作为装饰器子类,使用它们可以防止每次读取/发送数据时进行实际的写操作,代表着使用缓冲区。

我们有必要知道不带缓冲的操作,每读一个字节就要写入一个字节,由于涉及磁盘的IO操作相比内存的操作要慢很多,所以不带缓冲的流效率很低。带缓冲的流,可以一次读很多字节,但不向磁盘中写入,只是先放到内存里。等凑够了缓冲区大小的时候一次性写入磁盘,这种方式可以减少磁盘操作次数,速度就会提高很多!

同时正因为它们实现了缓冲功能,所以要注意在使用BufferedOutputStream写完数据后,要调用flush()方法或close()方法,强行将缓冲区中的数据写出。否则可能无法写出数据。与之相似还BufferedReaderBufferedWriter两个类。

现在就可以回答在本文的开头提出的问题:

BufferedInputStreamBufferedOutputStream类就是实现了缓冲功能的输入流/输出流。使用带缓冲的输入输出流,效率更高,速度更快。

总结:

  • BufferedInputStream 是缓冲输入流。它继承于FilterInputStream

  • BufferedInputStream 的作用是为另一个输入流添加一些功能,例如,提供“缓冲功能”以及支持mark()标记reset()重置方法

  • BufferedInputStream 本质上是通过一个内部缓冲区数组实现的。例如,在新建某输入流对应的BufferedInputStream后,当我们通过read()读取输入流的数据时,BufferedInputStream会将该输入流的数据分批的填入到缓冲区中。每当缓冲区中的数据被读完之后,输入流会再次填充数据缓冲区;如此反复,直到我们读完输入流数据位置。

BufferedInputStream API简介

源码关键字段分析

private static int defaultBufferSize = 8192;//内置缓存字节数组的大小 8KB
    
protected volatile byte buf[];  //内置缓存字节数组
    
protected int count;    //当前buf中的字节总数、注意不是底层字节输入流的源中字节总数
    
protected int pos;      //当前buf中下一个被读取的字节下标
    
protected int markpos = -1; //最后一次调用mark(int readLimit)方法记录的buf中下一个被读取的字节的位置
    
protected int marklimit;    //调用mark后、在后续调用reset()方法失败之前云寻的从in中读取的最大数据量、用于限制被标记后buffer的最大值

 

构造函数

BufferedInputStream(InputStream in) //使用默认buf大小、底层字节输入流构建bis 

BufferedInputStream(InputStream in, int size) //使用指定buf大小、底层字节输入流构建bis  

 

一般方法介绍

int available();  //返回底层流对应的源中有效可供读取的字节数      
  
void close();  //关闭此流、释放与此流有关的所有资源  
  
boolean markSupport();  //查看此流是否支持mark
  
void mark(int readLimit); //标记当前buf中读取下一个字节的下标  
  
int read();  //读取buf中下一个字节  
  
int read(byte[] b, int off, int len);  //读取buf中下一个字节  
  
void reset();   //重置最后一次调用mark标记的buf中的位子  
  
long skip(long n);  //跳过n个字节、 不仅仅是buf中的有效字节、也包括in的源中的字节 

 

BufferedOutputStream API简介

关键字段

protected byte[] buf;   //内置缓存字节数组、用于存放程序要写入out的字节  
  
protected int count;   //内置缓存字节数组中现有字节总数 

 

构造函数

BufferedOutputStream(OutputStream out); //使用默认大小、底层字节输出流构造bos。默认缓冲大小是 8192 字节( 8KB )
  
BufferedOutputStream(OutputStream out, int size);  //使用指定大小、底层字节输出流构造bos  

 

构造函数源码:

/**
  * Creates a new buffered output stream to write data to the
  * specified underlying output stream.
  * @param   out   the underlying output stream.
  */
 public BufferedOutputStream(OutputStream out) {
     this(out, 8192);
 }

 /**
  * Creates a new buffered output stream to write data to the
  * specified underlying output stream with the specified buffer
  * size.
  *
  * @param   out    the underlying output stream.
  * @param   size   the buffer size.
  * @exception IllegalArgumentException if size <= 0.
  */
 public BufferedOutputStream(OutputStream out, int size) {
     super(out);
     if (size <= 0) {
         throw new IllegalArgumentException("Buffer size <= 0");
     }
     buf = new byte[size];
 }

 

一般方法

//在这里提一句,`BufferedOutputStream`没有自己的`close`方法,
//当他调用父类`FilterOutputStrem`的方法关闭时,会间接调用自己实现的`flush`方法将buf中残存的字节flush到out中,
//再`out.flush()`到目的地中,DataOutputStream也是如此。
void flush(); 将写入bos中的数据flush到out指定的目的地中、注意这里不是flush到out中、因为其内部又调用了out.flush() write(byte b); 将一个字节写入到buf中 write(byte[] b, int off, int len); 将b的一部分写入buf中

 

那么什么时候flush()才有效呢?
答案是:当OutputStream是BufferedOutputStream时。

当写文件需要flush()的效果时,需要
FileOutputStream fos = new FileOutputStream("c:\a.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
也就是说,需要将FileOutputStream作为BufferedOutputStream构造函数的参数传入,然后对BufferedOutputStream进行写入操作,才能利用缓冲及flush()。

查看BufferedOutputStream的源代码,发现所谓的buffer其实就是一个byte[]。
BufferedOutputStream的每一次write其实是将内容写入byte[],当buffer容量到达上限时,会触发真正的磁盘写入。
而另一种触发磁盘写入的办法就是调用flush()了。

1.BufferedOutputStreamclose()时会自动flush
2.BufferedOutputStream在不调用close()的情况下,缓冲区不满,又需要把缓冲区的内容写入到文件或通过网络发送到别的机器时,才需要调用flush.

用缓冲流复制文件

 1 package com.app;
 2 import java.io.BufferedInputStream;
 3 import java.io.BufferedOutputStream;
 4 import java.io.File;
 5 import java.io.FileInputStream;
 6 import java.io.FileNotFoundException;
 7 import java.io.FileOutputStream;
 8 import java.io.IOException;
 9 import java.io.InputStream;
10 import java.io.OutputStream;
11 
12 
13 public class A3 {
14 
15     public static void main(String[] args) throws IOException {
16 
17         String filePath = "F:/123.png" ;
18         String filePath2 = "F:/abc.png" ;
19         File file = new File( filePath ) ;
20         File file2 = new File( filePath2 ) ;
21         copyFile( file , file2 );
22 
23     }
24     
25     /**
26      * 复制文件
27      * @param oldFile
28      * @param newFile
29      */
30     public static void copyFile( File oldFile , File newFile){
31         InputStream inputStream = null ;
32         BufferedInputStream bufferedInputStream = null ;
33 
34         OutputStream outputStream = null ;
35         BufferedOutputStream bufferedOutputStream = null ;
36 
37         try {
38             inputStream = new FileInputStream( oldFile ) ;
39             bufferedInputStream = new BufferedInputStream( inputStream ) ;
40 
41             outputStream = new FileOutputStream( newFile ) ;
42             bufferedOutputStream = new BufferedOutputStream( outputStream ) ;
43 
44             byte[] b=new byte[1024];   //代表一次最多读取1KB的内容
45 
46             int length = 0 ; //代表实际读取的字节数
47             while( (length = bufferedInputStream.read( b ) )!= -1 ){
48                 //length 代表实际读取的字节数
49                 bufferedOutputStream.write(b, 0, length );
50             }
51             //缓冲区的内容写入到文件
52             bufferedOutputStream.flush();
53         } catch (FileNotFoundException e) {
54             e.printStackTrace();
55         }catch (IOException e) {
56             e.printStackTrace();
57         }finally {
58 
59             if( bufferedOutputStream != null ){
60                 try {
61                     bufferedOutputStream.close();
62                 } catch (IOException e) {
63                     e.printStackTrace();
64                 }
65             }
66 
67             if( bufferedInputStream != null){
68                 try {
69                     bufferedInputStream.close();
70                 } catch (IOException e) {
71                     e.printStackTrace();
72                 }
73             }
74             
75             if( inputStream != null ){
76                 try {
77                     inputStream.close();
78                 } catch (IOException e) {
79                     e.printStackTrace();
80                 }
81             }
82             
83             if ( outputStream != null ) {
84                 try {
85                     outputStream.close();
86                 } catch (IOException e) {
87                     e.printStackTrace();
88                 }
89             }
90 
91         }
92     }
93 }

 

如何正确的关闭流

在上面的代码中,我们关闭流的代码是这样写的。

finally {

            if( bufferedOutputStream != null ){
                try {
                    bufferedOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if( bufferedInputStream != null){
                try {
                    bufferedInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if( inputStream != null ){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if ( outputStream != null ) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
思考:在处理流关闭完成后,我们还需要关闭节点流吗?

让我们带着问题去看源码:

bufferedOutputStream.close();

   /**
 * Closes this input stream and releases any system resources
 * associated with the stream.
 * Once the stream has been closed, further read(), available(), reset(),
 * or skip() invocations will throw an IOException.
 * Closing a previously closed stream has no effect.
 *
 * @exception  IOException  if an I/O error occurs.
 */
public void close() throws IOException {
    byte[] buffer;
    while ( (buffer = buf) != null) {
        if (bufUpdater.compareAndSet(this, buffer, null)) {
            InputStream input = in;
            in = null;
            if (input != null)
                input.close();
            return;
        }
        // Else retry in case a new buf was CASed in fill()
    }
}

 

  • close()方法的作用
    1、关闭输入流,并且释放系统资源
    2、BufferedInputStream装饰一个 InputStream 使之具有缓冲功能,is要关闭只需要调用最终被装饰出的对象的 close()方法即可,因为它最终会调用真正数据源对象的 close()方法。因此,可以只调用外层流的close方法关闭其装饰的内层流。

那么如果我们想逐个关闭流,我们该怎么做?

答案是:先关闭外层流,再关闭内层流。一般情况下是:先打开的后关闭,后打开的先关闭;另一种情况:看依赖关系,如果流a依赖流b,应该先关闭流a,再关闭流b。例如处理流a依赖节点流b,应该先关闭处理流a,再关闭节点流b

看懂了怎么正确的关闭流之后,那么我们就可以优化上面的代码了,只关闭外层的处理流。

finally {

            if( bufferedOutputStream != null ){
                try {
                    bufferedOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if( bufferedInputStream != null){
                try {
                    bufferedInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

 

BufferedReader

  • 构造函数
BufferedReader(Reader in, int sz) //创建一个使用指定大小输入缓冲区的缓冲字符输入流。 

BufferedReader(Reader in) //创建一个使用默认大小输入缓冲区的缓冲字符输入流。

 

  • 方法
int  read()  //读取单个字符。
int  read(char[] cbuf, int off, int len)  //将字符读入数组的某一部分。
String  readLine()  //读取一个文本行。
boolean  ready()  //判断此流是否已准备好被读取。
void  reset()  //将流重置到最新的标记。
long  skip(long n)  //跳过字符。
void  close() //关闭该流并释放与之关联的所有资源。
void  mark(int readAheadLimit) //标记流中的当前位置。
boolean  markSupported() //判断此流是否支持 mark() 操作(它一定支持)。

 

BufferedWriter

  • 构造函数
BufferedWriter(Writer out, int sz) //创建一个使用给定大小输出缓冲区的新缓冲字符输出流。

BufferedWriter(Writer out) //建一个使用默认大小输出缓冲区的缓冲字符输出流。

 

  • 方法
void  close()  // 关闭此流,但要先刷新它。
void  flush()  //刷新该流的缓冲。
void  newLine() //写入一个行分隔符。
void  write(char[] cbuf, int off, int len) //写入字符数组的某一部分。
void  write(int c) //写入单个字符。
void  write(String s, int off, int len) //写入字符串的某一部分。

 

实战演练
复制F盘里面的一个txt文本

 1 package com.app;
 2 
 3 import java.io.BufferedReader;
 4 import java.io.BufferedWriter;
 5 import java.io.File;
 6 import java.io.FileNotFoundException;
 7 import java.io.FileReader;
 8 import java.io.FileWriter;
 9 import java.io.IOException;
10 import java.io.Reader;
11 import java.io.Writer;
12 
13 public class A4 {
14     public static void main(String[] args) {
15 
16         String filePath = "F:/123.txt" ;
17         String filePath2 = "F:/abc.txt" ;
18 
19         File file = new File( filePath ) ;
20         File file2 = new File( filePath2 ) ;
21         copyFile( file , file2 );
22     }
23 
24     private static void copyFile( File oldFile , File newFile ){
25         Reader reader = null ;
26         BufferedReader bufferedReader = null ;
27 
28         Writer writer = null ;
29         BufferedWriter bufferedWriter  = null ;
30         try {
31             reader = new FileReader( oldFile ) ;
32             bufferedReader = new BufferedReader( reader ) ;
33 
34             writer = new FileWriter( newFile ) ;
35             bufferedWriter = new BufferedWriter( writer ) ;
36 
37             String result = null ; //每次读取一行的内容
38             while (  (result = bufferedReader.readLine() ) != null ){
39                 bufferedWriter.write( result );  //把内容写入文件
40                 bufferedWriter.newLine();  //换行,result 是一行数据,所以没写一行就要换行 
41             }
42 
43             bufferedWriter.flush();  //强制把数组内容写入文件
44 
45         } catch (FileNotFoundException e) {
46             e.printStackTrace();
47         }catch (IOException e) {
48             e.printStackTrace();
49         }finally {
50             try {
51                 bufferedWriter.close();  //关闭输出流
52             } catch (IOException e) {
53                 e.printStackTrace();
54             }
55 
56             try {
57                 bufferedReader.close();  //关闭输入流
58             } catch (IOException e) {
59                 e.printStackTrace();
60             }
61         }
62     }
63 }

 转换流

 

InputStreamReader简介

 

InputStreamReader 是字符流Reader的子类,是字节流通向字符流的桥梁。你可以在构造器重指定编码的方式,如果不指定的话将采用底层操作系统的默认编码方式,例如 GBK 等。要启用从字节到字符的有效转换,可以提前从底层流读取更多的字节,使其超过满足当前读取操作所需的字节。一次只读一个字符。

 

  • InputStreamReader构造函数

 

InputStreamReader(Inputstream  in) //创建一个使用默认字符集的 InputStreamReader。

InputStreamReader(Inputstream  in,Charset cs) //创建使用给定字符集的 InputStreamReader。

InputStreamReader(InputStream in, CharsetDecoder dec) //创建使用给定字符集解码器的 InputStreamReader。

InputStreamReader(InputStream in, String charsetName)  //创建使用指定字符集的 InputStreamReader。

 

 

  • 一般方法

 

void  close() // 关闭该流并释放与之关联的所有资源。

String  getEncoding() //返回此流使用的字符编码的名称。

int  read()  //读取单个字符。

int  read(char[] cbuf, int offset, int length) //将字符读入数组中的某一部分。

boolean  ready() //判断此流是否已经准备好用于读取。

 

 

OutputStreamWriter简介

 

OutputStreamWriter 是字符流Writer的子类,是字符流通向字节流的桥梁。每次调用 write()方法都会导致在给定字符(或字符集)上调用编码转换器。在写入底层输出流之前,得到的这些字节将在缓冲区中累积。一次只写一个字符。

 

  • OutputStreamWriter构造函数

 

OutputStreamWriter(OutputStream out) //创建使用默认字符编码的 OutputStreamWriter

OutputStreamWriter(OutputStream out, String charsetName) //创建使用指定字符集的 OutputStreamWriter。

OutputStreamWriter(OutputStream out, Charset cs) //创建使用给定字符集的 OutputStreamWriter。

OutputStreamWriter(OutputStream out, CharsetEncoder enc) //创建使用给定字符集编码器的 OutputStreamWriter。

 

 

  • 一般方法

 

void  write(int c)   //写入的字符长度

void  write(char cbuf[])  //写入的字符数组

void  write(String str)  //写入的字符串

void  write(String str, int off, int len)  //应该写入的字符串,开始写入的索引位置,写入的长度

void  close() //关闭该流并释放与之关联的所有资源。

 

 

需要注意的事项

 

InputStreamReaderOutputStreamWriter实现从字节流到字符流之间的转换,使得流的处理效率得到提升,但是如果我们想要达到最大的效率,我们应该考虑使用缓冲字符流包装转换流的思路来解决问题。比如:

 

BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

 

 

实战演练,复制文本

 

 1 package com.app;
 2 
 3 import java.io.File;
 4 import java.io.FileInputStream;
 5 import java.io.FileNotFoundException;
 6 import java.io.FileOutputStream;
 7 import java.io.IOException;
 8 import java.io.InputStream;
 9 import java.io.InputStreamReader;
10 import java.io.OutputStream;
11 import java.io.OutputStreamWriter;
12 
13 public class A5 {
14 
15     public static void main(String[] args) {
16         String filePath = "F:/123.txt" ;
17         String filePath2 = "F:/abc.txt" ;
18         File file = new File( filePath ) ;
19         File file2 = new File( filePath2 ) ;
20         copyFile( file , file2 );
21 
22     }
23 
24     private static void copyFile( File oldFile , File newFile ){
25         InputStream inputStream = null ;
26         InputStreamReader inputStreamReader = null ;
27 
28         OutputStream outputStream = null ;
29         OutputStreamWriter outputStreamWriter = null ;
30 
31         try {
32             inputStream = new FileInputStream( oldFile ) ; //创建输入流
33             inputStreamReader = new InputStreamReader( inputStream ) ; //创建转换输入流
34 
35             outputStream = new FileOutputStream( newFile ) ; //创建输出流
36             outputStreamWriter = new OutputStreamWriter( outputStream ) ; //创建转换输出流
37 
38             int result = 0 ;
39 
40             while( (result = inputStreamReader.read()) != -1){  //一次只读一个字符
41                 outputStreamWriter.write( result ); //一次只写一个字符
42             }
43 
44             outputStreamWriter.flush();  //强制把缓冲写入文件
45 
46         } catch (FileNotFoundException e) {
47             e.printStackTrace();
48         }catch (IOException e) {
49             e.printStackTrace();
50         }finally{
51 
52             if ( outputStreamWriter != null) {
53                 try {
54                     outputStreamWriter.close();
55                 } catch (IOException e) {
56                     e.printStackTrace();
57                 }
58             }
59 
60             if ( inputStreamReader != null ) {
61                 try {
62                     inputStreamReader.close();
63                 } catch (IOException e) {
64                     e.printStackTrace();
65                 }
66             }
67         }
68 
69     }
70 }

 

java IO流全面总结

标签:无法   stack   style   构建   12px   otf   写文件   缓冲流   字节流   

原文地址:http://www.cnblogs.com/syp172654682/p/8007407.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!