Java學習之IO流

概述:

1、IO流:即Input Output的縮寫。

2、特點:
1)IO流用來處理設備間的數據傳輸。
2)Java對數據的操作是通過流的方式。
3)Java用于操作流的對象都在IO包中。
4)流按操作數據分為兩種:字節流和字符流。
5)流按流向分為:輸入流和輸出流。

注意:流只能操作數據,而不能操作文件。

3、IO流的常用基類:
1)字節流的抽象基流:InputStream和OutputStream
2)字符流的抽象基流:Reader和Writer

注:此四個類派生出來的子類名稱都是以父類名作為子類名的后綴,以前綴為其功能;如InputStream子類FileInputStream和Reader子類FileReader

一、字符流

1、簡述:

1、字符流中的對象融合了編碼表。使用的是默認的編碼,即當前系統的編碼。

2、字符流只用于處理文字數據,而字節流可以處理媒體數據。

3、既然IO流是用于操作數據的,那么數據的最常見體現形式是文件。專門用于操作文件的Writer子類對象:FileWriter ---> 后綴名為父類名,前綴名為流對象的功能。

2、字符流的讀寫

1、寫入字符流:

1)創建一個FileWriter對象,該對象一被初始化,就必須要明確被操作的文件。且該目錄下如果已有同名文件,則同名文件將被覆蓋。其實該步就是在明確數據要存放的目的地。

2)調用write(String s)方法,將字符串寫入到流中。

3)調用flush()方法,刷新該流的緩沖,將數據刷新到目的地中。

4)調用close()方法,是關閉流資源。但是關閉前會刷新一次內部的緩沖數據,并將數據刷新到目的地中。

注:
--->close()flush()區別:flush()刷新后,流可以繼續使用;而close()刷新后,將會關閉流,不可再寫入字符流。

--->其實java自身不能寫入數據,而是調用系統內部方式完成數據的書寫,使用系統資源后,一定要關閉資源。

--->數據的續寫是通過構造函數FileWriter(String s,boolean append),根據給定文件名及指示是否附加寫入數據的boolean值來構造FileWriter對象。

2、讀取字符流:
1)創建一個文件讀取流對象,和指定名稱的文件相關聯。要保證該文件已經存在,若不存在,將會發生異常FileNotFoundException。

2)調用讀取流對象的read()方法。
read() :一次讀一個字符,且會繼續往下讀。
第一種方式:讀取單個字符。
第二種方式:通過字符數組進行讀取。

3)讀取后要將流資源關閉。

示例:

import java.io.*;  
class IODemo{  
    public static void main(String[] args){  
        FileWriter fw =null;  
        FileReader fr = null;  
        //檢測異常  
        try{  
            //創建讀寫對象,并指定目的地  
            fw = new FileWriter("Demo.txt");  
            fr = new FileReader("Demo.txt");  
            //將數據寫入到指定文件中  
            for(int i=0;i<5;i++){  
                fw.write("abcde" + i + "\r\n");  
                fw.flush();  
            }  
            //從指定文件中讀取數據  
            int len = 0;  
            char[] ch = new char[1024];  
            while((len=fr.read(ch))!=-1){  
                System.out.println(new String(ch,0,len));  
            }  
        }  
        //捕獲異常  
        catch (IOException e){  
            throw new RuntimeException("讀寫文件失敗");  
        }  
        //最終關閉兩個流資源  
        finally{  
            if(fw!=null){  
                try{  
                    fw.close();  
                }  
                catch (IOException e){  
                    throw new RuntimeException("關閉流資源失敗。");  
                }  
            }  
            if(fr!=null){  
                try{  
                    fr.close();  
                }  
                catch (IOException e){  
                    throw new RuntimeException("讀取流資源失敗。");  
                }  
            }  
        }  
    }  
}

2、文件的拷貝:

原理:
其實就是將一個磁盤下的文件數據存儲到另一個磁盤的一個文件中。

步驟:
1、在D盤上創建一個文件,用于存儲E盤文件中的數據
2、定義讀取流和E盤文件關聯
3、通過不斷讀寫完成數據存儲
方式一:讀取一個字符,存入一個字符
方式二:先將讀取的數據存入到內存中,再將存入的字符取出寫入D盤
4、關閉流資源:輸入流資源和輸出流資源。

示意圖

示例:

import java.io.*;  
class CopyDemo  
{  
    public static void main(String [] args)  
    {  
        FileReader fr = null;  
        FileWriter fw = null;  
        try{  
            fr = new FileReader("E:\\Dmeo.txt");  
            fw = new FileWriter("D:\\Dmeo.txt");  
            char[] ch = new char[1024];  
            int len = 0;  
            //用ch將讀取的文件和寫入的文件關聯起來  
            while((len=fr.read(ch))!=-1)  
            {  
                fw.write(ch,0,ch.length);  
            }  
        }  
        catch (IOException e){  
            throw new RuntimeException("文件讀寫失敗");  
        }  
        finally{  
            if(fr!=null){  
                try{  
                    fr.close();  
                }  
                catch (IOException e){  
                    throw new RuntimeException("讀取流資源關閉失敗。");  
                }  
            }  
            if(fw!=null){  
                try{  
                    fw.close();  
                }  
                catch (IOException e){  
                    throw new RuntimeException("寫入流資源關閉失敗。");  
                }  
            }  
        }  
    }  
}

3、BufferedWriter和BufferedReader:字符流緩沖區

1、緩沖區的出現:提高了流的讀寫效率,所以在緩沖區創建前,要先創建流對象,即先將流對象初始化到構造函數中。

2、緩沖技術原理:此對象中封裝了數組,將數據存入,在一次性取出。

3、寫入流緩沖區BufferedWriter的步驟:
1)創建一個字符寫入流對象
2)為提高字符寫入流效率,加入緩沖技術,只要將需要被提高效率的流對象作為參數傳遞給緩沖區的構造函數即可。
注意,只要用到緩沖去就需要刷新。
3)其實關閉緩沖區就是在關閉緩沖區中的流對象。
--->該緩沖區中提供了一個跨平臺的換行符:newLine()

4、讀取流緩沖區BufferedReader的步驟:
1)創建一個讀取流對象和文件關聯
2)為提高效率,加入緩沖技術,將字符讀取流對象作為參數傳遞給緩沖對象的構造函數。
3)該緩沖區提供了一個一次讀一行的方法readLine(),方便與對文本數據的獲取,當返回null時,表示讀到文件末尾。
readLine() 方法返回的時只返回回車符之前的數據內容,并不返回回車符,即讀取的內容中不包含任何行終止符(回車符和換行符)。
readLine() 方法原理:無論是讀一行,或讀取多個字符,其實最終都是在硬盤上一個一個讀取。所以最終使用的還是read方法一次讀一個。

示意圖

5、使用緩沖技術拷貝文件,提高效率:

示例:

import java.io.*;  
class CopyBufferedDemo{  
    public static void main(String[] args) {  
        BufferedReader bufr = null;  
        BufferedWriter bufw = null;  
        try{  
            //創建緩沖區,將讀寫流對象作為參數傳入  
            bufr = new BufferedReader(new FileReader("D:\\JAVA\\IO\\buffered\\myJava.txt"));  
            bufw = new BufferedWriter(new FileWriter("D:\\JAVA\\IO\\文件拷貝\\myJava.txt"));  
            //定義字符串,通過readLine一次讀一行,并存入緩沖區  
            String line = null;  
            while((line=bufr.readLine())!=null)  
            {  
                //將讀取到緩沖區的數據通過line存入寫入流中  
                bufw.write(line);  
                //換行符方法  
                bufw.newLine();  
                //將數據刷新到指定文件中  
                bufw.flush();  
            }  
        }  
        catch (IOException e){  
            throw new RuntimeException("讀寫文件失敗。");  
        }  
        finally{  
            if(bufr!=null){  
                try{  
                    bufr.close();  
                }  
                catch (IOException e){  
                    throw new RuntimeException("讀取流資源關閉失敗。");  
                }  
            }  
            if(bufw!=null){  
                try{  
                    bufw.close();  
                }  
                catch (IOException e){  
                    throw new RuntimeException("寫入流資源關閉失敗。");  
                }  
            }  
        }  
    }  
}

6、自定義BufferedReader:
原理:
可根據BufferedReader類中特有發那個發readLine()的原理,自定義一個類中包含相同功能的方法

步驟:
a.初始化自定義的類,加入流對象。
b.定義一個臨時容器,原BufferedReader封裝的是字符數組,此類中可定義一個StringBuilder的容器,最終可實現字符串的提取。

import java.io.*;  
//自定義一個功能與BufferedReader相似的類,繼承Reader  
class MyBufferedReader extends Reader  
{  
    //定義變量,用于全局  
    private Reader r;  
    //構造函數,傳入讀取流對象  
    MyBufferedReader(Reader r)  
    {  
        this.r = r;  
    }  
    //復寫readLine放,并將異常拋出,有調用者處理  
    public String MyReadLine()throws IOException  
    {  
        //定義存儲字符串的容器  
        StringBuilder sb = new StringBuilder();  
        int ch = 0;  
        //read()方法遍歷文件,當達末尾時判斷是否為-1結束循環  
        while((ch=r.read())!=-1)  
        {  
            //判斷是否有行終止符,有則不存入  
            if(ch=='\r')  
                continue;  
            //當讀到結尾,需要將緩沖區中存放的數據變為字符串返回  
            if(ch=='\n')  
                return sb.toString();  
            else  
                //將讀取的字符存入容器  
                sb.append((char)ch);  
        }  
        //對于最后一行沒有終止符的情況,需要將讀取到的加入容器中  
        if(sb.length()!=0)  
            return sb.toString();  
        //讀到結尾處,返回null  
        return null;  
    }  
    /* 
    覆蓋抽象方法: 
    */  
    public int read(char[] cbuf,int off,int len)throws IOException  
    {  
        return r.read(cbuf,off,len);  
    }  
    //覆蓋close方法  
    public void close()throws IOException  
    {  
        r.close();  
    }  
    //創建自定義close方法  
    public void MyClose()throws IOException  
    {  
        r.close();  
    }  
}  
//測試  
class MyBufferedDemo{  
    public static void main(String [] args){  
        MyBufferedReader mbr = null;  
        //檢測異常  
        try{  
            mbr = new MyBufferedReader(new FileReader("Demo.txt"));  
            String line = null;  
            //遍歷文件,讀取數據  
            while ((line=mbr.MyReadLine())!=null){  
                System.out.println(line);  
            }  
        }  
        //捕獲異常  
        catch (IOException e){  
            throw new RuntimeException("讀取文件失敗。");  
        }  
        //最終關閉資源  
        finally{  
            if(mbr!=null){  
                try{  
                    mbr.MyClose();  
                }  
                catch (IOException e){  
                    throw new RuntimeException("讀取流關閉失敗。");  
                }  
            }  
        }  
    }  
}

7、LineNumberReader

在BufferedReader中有個直接的子類LineNumberReader,其中有特有的方法獲取和設置行號:
setLineNumber()和getLineNumber()

示例:

......  
try  
{  
    fr = new FileReader("myJava.txt");  
    lnr = new LineNumberReader(fr);  
    lnr.setLineNumber(100);  
    String line = null;  
    while((line=lnr.readLine())!=null)  
    {  
        System.out.println(lnr.getLineNumber() + ":  " + line);  
    }  
}  
......  

通過這個例子,就可以引出裝飾設計模式

4、裝飾設計模式:

1、簡述:
當想對已有對象進行功能增強是,可定義類:將已有對象傳入,基于已有對象的功能,并提供加強功能,那么自定義的該類稱之為裝飾類。即對原有類進行了優化。

2、特點:
裝飾類通常都會通過構造方法接收被裝飾的對象,并基于被裝飾的對i型那個的功能提供更強的功能。

3、裝飾和繼承的區別:

1)裝飾模式比繼承要靈活,通過避免了繼承體系的臃腫,且降低了類與類間的關系。

2)裝飾類因為增強已有對象,具備的功能和已有的是相同的,只不過提供了更強的功能,所以裝飾類和被裝飾的類通常都是屬于一個體系。

3)從繼承結構轉為組合結構。

注:
在定義類的時候,不要以繼承為主;可通過裝飾設計模式進行增強類功能。靈活性較強,當裝飾類中的功能不適合,可再使用被裝飾類的功能。
要繼承相應的父類,就需要將所有的抽象方法實現,或交給子類實現。

示例:其中MyBufferedReader的例子就是最好的裝飾設計模式的例子。

二、字節流

1、概述:

1、字節流和字符流的原理是相似的,只不過字節流可以對媒體進行操作。

2、由于媒體數據中都是以字節存儲的,所以,字節流對象可直接對媒體進行操作,而不用再進行刷流動作。

3、讀寫字節流:
InputStream ---> 輸入流(讀)
OutputStream ---> 輸出流(寫)

4、為何不用進行刷流動作:
因為字節流操作的是字節,即數據的最小單位,不需要像字符流一樣要進行轉換為字節??芍苯訉⒆止潓懭氲街付ㄎ募?,但是需要在寫代碼的時候,如果有字符串,要將字符串轉為字節數組再進行操作。

5、特有方法:
int available() ---> 放回數據字節的長度,包含終止符

在定義字節數組長度的時候,可以用到這個方法:
byte[] = new byte[fos.available()] (fos為字節流對象)

但是,對于這個方法要慎用,如果字節過大(幾個G),那么如此大的數組就會損壞內存,超過jvm所承受的大小(指定內存為64M)。

2、拷貝媒體文件:

1、思路:
1)用字節流讀取流對象和媒體文件相關聯
2)用字節寫入流對象,創建一個媒體文件,用于存儲獲取到的媒體文件數據
3)通過循環讀寫,完成數據的存儲
4)關閉資源

示例:

import java.io.*;  
class CopyPic  
{  
    public static void main(String[] args)   
    {  
        //創建流對象引用  
        FileOutputStream fos = null;  
        FileInputStream fis = null;  
        try{  
            //創建讀寫流對象  
            fos = new FileOutputStream("2.gif");  
            fis = new FileInputStream("1.gif");  
            int len = 0;  
            //定義字節數組,存儲讀取的字節流  
            byte[] arr = new byte[1024];  
            //循環讀寫流,完成數據存儲  
            while((len=fis.read(arr))!=-1){  
                fos.write(arr,0,len);  
            }  
        }catch (IOException e){  
            throw new RuntimeException("復制圖片失敗");  
        }  
        //最終關閉資源  
        finally{  
            if(fos!=null){  
                try{  
                    fos.close();  
                }catch (IOException e){  
                    throw new RuntimeException("寫入流關閉失敗");  
                }  
            }  
            if(fos!=null){  
                try{  
                    fis.close();  
                }catch (IOException e){  
                    throw new RuntimeException("讀取流關閉失敗");  
                }         
            }  
        }  
    }  
}

3、字節流緩沖區:

1、讀寫特點:
read() :會將字節byte型值提升為int型值
write() :會將int型強轉為byte型,即保留二進制數的最后八位。

2、原理:
將數據拷貝一部分,讀取一部分,循環,直到數據全部讀取完畢。
1)先從數據中抓取固定數組長度的字節,存入定義的數組中,再通過然后再通過read()方法讀取數組中的元素,存入緩沖區
2)循環這個動作,知道最后取出一組數據存入數組,可能數組并未填滿,同樣也取出包含的元素
3)每次取出的時候,都有一個指針在移動,取到數組結尾就自動回到數組頭部,這樣指針在自增
4)取出的時候,數組中的元素再減少,取出一個,就減少一個,直到減到0即數組取完
5)到了文件的結尾處,存入最后一組數據,當取完數組中的元素,就會減少到0,這是全部數據就取完了

3、示意圖:


示意圖

4、自定義字節流緩沖區:
思路:
1、定義一個固定長度的數組
2、定義一個指針和計數器用于讀取數組長度,和計數數組元素是否取完為0
3、每次將字節數據存入元素要先將數組中的元素取完

注:
取出的是byte型,返回的是int型,這里存在提升的動作,當byte中的八位全為1的時候是byte的-1,提升為int類型,就變為int型的-1,read循環條件就結束了變為-1的原因是由于在提升時,將byte的八位前都補的是1,即32位的數都是1,即為int型的-1了。

如何保證提升后的最后八位仍為1呢?
就需要將前24位補0,就可以保留原字節數據不變,又可以避免轉為int型出現-1的情況;
那么要如何做呢?
這就需要將提升為int的數據和前24位為0,后八位仍為原字節數據的這個值做與運算。即和255做與運算即可

import java.io.*;  
class MyBufferedInputStream  
{  
    private InputStream in;  
    byte[] by = new byte[1024*4];  
    private int pos=0,count=0;  
    //傳入加強的類  
    MyBufferedInputStream(InputStream in)  
    {  
        this.in = in;  
    }  
    //自定義讀取方法  
    public int myRead()throws IOException  
    {  
        //先判斷計數器  
        if(count==0)  
        {  
            //計數器為0則存入數據  
            count = in.read(by);  
            //計數器為負則返回-1,說明結束數據讀取  
            if(count<0)  
                return -1;  
            //每次從數組中讀取數據完,指針要歸零,重新移動指針  
            pos = 0;  
            //獲取存入數組的元素,并需要讓指針和計數器相應變化  
            byte b = by[pos];  
            count--;  
            pos++;  
            //返回讀取的值,需要與運算  
            return b&255;  
        }  
        //計數器大于零時,不需要存數據,只需讀取  
        else if(count>0)  
        {  
            byte b = by[pos];  
            count--;  
            pos++;  
            return b&0xff;  
        }  
        //為-1時即到數據結尾  
        return -1;  
    }  
    public void myClose()throws IOException  
    {  
        in.close();  
    }  
}

三、流操作規律

1、讀取鍵盤錄入:

1、System.out :對應的是標準的輸出設備 ---> 控制臺
System.in : 對應的是標準的輸出設備 ---> 鍵盤
in字段返回的是IputStream類型,其read()方法在未讀取到數據時,一直阻塞

2、如何使用readLine方法完成鍵盤錄入一行數據的讀?。?br> 說明:
readLine()方法是字符流中BufferedReader類的方法;而鍵盤錄入的read()方法是字節流IputStream的方法

那么,如何將字節流轉成字符流在使用字符流緩沖區的readLine()的方法呢?
這就需要使用InputStreamReader對象。

3、轉換流:

3.1、InputStreamReader:讀取轉換流
a.獲取鍵盤錄入對象: --->InputStream in = System.in;
b.將字節流對象轉換成字符流對象,使用轉換流InputStreamReader: --->

InputStreamReader isr = new InputStreamReader(in);

c.為提高效率,將字符串進行緩沖區技術操作,使用BufferedReader: --->

BufferedReader bufw = new BufferedReader(isr);

d.之后就可以使用readLine()方法讀取錄入的一行數據了。

3.2、OutputStreamWriter:寫入轉換流 ---> 和讀取轉換流同理,即使用對應的Writer的子類

import java.io.*;  
class StreamWriter{  
    public static void main(String[] args){  
        BufferedReader bfr = null;  
        BufferedWriter bfw = null;  
        try{  
            //最常見寫法,鍵盤錄入:  
            bfr = new BufferedReader(new InputStreamReader(System.in));  
            //輸出控制臺寫法  
            bfw = new BufferedWriter(new OutputStreamWriter(System.out));  
            String line = null;  
            while((line=bfr.readLine())!=null){  
                //自定義結束標記  
                if("over".equals(line))  
                    break;  
                bfw.write(line.toUpperCase());  
                bfw.newLine();  
                bfw.flush();  
            }  
        }catch (IOException e){  
            throw new RuntimeException("讀寫失敗。");  
        }  
        finally{  
            try{  
                if(bfr!=null)  
                    bfr.close();  
            }catch (IOException e){  
                throw new RuntimeException("讀取流關閉失敗。");  
            }  
            try{  
                if(bfw!=null)  
                    bfw.close();  
            }catch (IOException e){  
                throw new RuntimeException("寫入流關閉失敗。");  
            }  
        }  
    }  
}

2、流操作規律:

1、將鍵盤錄入的數據打印到屏幕上
源:鍵盤錄入 ---> 目的:控制臺

2、將鍵盤錄入的數據存入文件中
源:鍵盤錄入 ---> 目的:文件
---> 創建一個寫入(輸出)流對象文件作為參數,傳給轉換流(橋梁):OutputStreamWriter

3、要將一個文件的數據打印到控制臺:
源:文件 ---> 目的:控制臺
---> 創建一個讀取(輸入)流對象文件作為參數,傳給轉換流(橋梁):InputStreamReader

4、流操作基本規律:
在寫代碼過程中最痛苦的是:流對象較多,不知道該用哪一個,這就需要有如下的三點明確:
第一、明確源和目的:
源 :輸入流 ---> InputStream和Reader
目的:輸出流 ---> OutputStream和Writer

第二、明確操作的數據是否為純文本:
是:字符流
否:字節流

第三、當體系明確后,再明確具體要用哪個具體對象,通過設備來區分
源 設 備:內存(ArrayStream)、硬盤(FileStream)、鍵盤(System.in)
目的設備:內存(ArrayStream)、硬盤(FileStream)、控制臺(System.out)

可以在下面的需求體現中,發現元和目的是對應的,都為對應體系的一對對象。

5、需求體現:
5.1、將一個文本文件中的數據存入另一個文件,即復制文本
1)源:使用讀取流:InputStream和Reader
---> 明確體系:是否為文本文件:是,選Reader
---> 明確設備:明確要使用該體系中哪個對象:硬盤上一個文件。
即Reader體系中可操作文件的對象:FileReader
---> 是否需要高效:是,加入Reader體系中的緩沖區:BufferedReader

FileReader fr = new FileReader("a.txt");  
BufferedReader bufr = new BufferedReader(fr);  

2)目的:使用寫入流:OutputStream和Writer
---> 明確體系:是否為文本文件:是,選Writer
---> 明確設備:明確要使用該體系中哪個對象:硬盤上一個文件。即Writer體系中可操作對象的是FileWriter
---> 是否需要高效:是,加入Writer體系中的緩沖區:BufferedWriter

FileWriter fw = new FileWriter("b.txt");  
BufferedWriter bufw = new BufferedWriter(fw);  

5.2、將圖片復制到另一個文件夾中:
1)源:InputStream和Reader
圖片:字節流 ---> InputStream
設備:硬盤上的文件 ---> FileInputStream

2)目的:OutoutStream和Writer
圖片:字節流 ---> OutputStream
設備:硬盤上的文件 ---> FileOutputStream

5.3、將鍵盤錄入數據保存到一個文件中。
這個需求中有源和目的,都存在:
1)源:InputStream和Reader
純文本?是,字符流 ---> Reader
設備:鍵盤 ---> 對應的是System.in,是字節流
---> 為方便操作鍵盤的文本數據,轉成字符流;按照字符串操作是最方便的,所以既然明確了Reader,那么就將產生字節流System.in轉為字符流Reader,就需用Reader體系中的轉換流InputStreamReader

InutStreamReader isr = new InputStreamReader(System.in);

---> 是否需要高效:是,加入Reader體系中的緩沖區:BufferedReader;

BufferedReader bufr = new BufferedReader(isr);

2)目的:OutoutStream和Writer
純文本?是,字符流 ---> Writer
設備:硬盤上的文件 ---> FileWriter
---> 是否需要高效:是,加入Writer體系中的緩沖區:BufferedWriter;
即 BufferedWriter bufw = new BufferedWriter(fw);

5.4、擴展:
想要將錄入的數據按指定編碼表(UTF-8),將數據存入文件
1)源:InputStream和Reader
純文本?是,字符流 ---> Reader
設備:鍵盤 ---> 對應的是System.in,是字節流;需要用轉換流:InputStreamReader,

InputStreamReader isr = new InputStreamReader(System.in);

2)目的:OutoutStream和Writer
純文本?是,字符流 ---> Writer
設備:硬盤上的文件 ---> 應該用FileInputStream,但是存儲時,需要加入指定的編碼表,而指定編碼表只有轉換流可以指定,所以要使用的對象是:OutputStreamWriter;而轉換流對象要接收一個字節輸出流,且還可以操作文件的字節輸出流,即FileOutputStream
即:

OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d.txt","UTF-8"));

---> 是否需要高效:是,

BufferedWriter bufw = new BufferedWriter(osw);

記?。恨D換流何時使用:字符流和字節流間的橋梁,通常涉及字符編碼轉換時,需要用到轉換流。

示例:

import java.io.*;  
class FileStreamWriter{  
    public static void main(String[] args){  
        BufferedReader bfr = null;  
        BufferedWriter bfw = null;  
        try{  
            System.setIn(new FileInputStream("推薦書籍.txt"));  
            System.setOut(new PrintStream("copy.java"));  
            //最常見寫法,鍵盤錄入:  
            bfr = new BufferedReader(new InputStreamReader(System.in));  
            //輸出控制臺寫法  
            bfw = new BufferedWriter(new OutputStreamWriter(System.out));  
            //bfw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("d.txt"),"UTF-8"));  
            String line = null;  
            while((line=bfr.readLine())!=null){  
                //自定義結束標記  
                if("over".equals(line))  
                    break;  
                bfw.write(line.toUpperCase());  
                bfw.newLine();  
                bfw.flush();  
            }  
        }  
        catch (IOException e){  
            throw new RuntimeException("讀寫失敗。");  
        }  
        finally{  
            try{  
                if(bfr!=null)  
                    bfr.close();  
            }catch (IOException e){  
                throw new RuntimeException("讀取流關閉失敗。");  
            }  
            try{  
                if(bfw!=null)  
                    bfw.close();  
            }  
            catch (IOException e){  
                throw new RuntimeException("寫入流關閉失敗。");  
            }  
        }  
    }  
}

四、信息

1、異常的日志信息:

當程序在執行的時候,出現的問題是不希望直接打印給用戶看的,是需要作為文件存儲起來,方便程序員查看,并及時調整的。

示例:

import java.io.*;  
import java.util.*;  
import java.text.*;  
  
class ExceptionInfo  
{  
    public static void main(String[] args){  
        try{  
            int[] arr = new int[2];  
            System.out.println(arr[3]);  
        }catch (IOException e){  
            try{  
                Date d = new Date();  
                SimpleDateFormat sfd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
                String s = sfd.format(d);  
  
                PrintStream pf = new PrintStream("exception.log");  
                pf.println(s);  
                System.set(ps);  
            }  
            catch (IOException ex){  
                throw new RuntimeException("日志文件創建失敗。");  
            }  
            e.printStackTrace(System.out);  
        }  
    }  
}

2、系統信息:

獲取系統信息:

Properties getProperties()

將信息輸出到指定輸出流中

void  list(PrintStream out)

將輸出流中數據存入指定文件中

new PrintStream("systeminfo.txt")

示例:

import java.util.*;  
import java.io.*;  
  
class SystemInfo   
{  
    public static void main(String[] args)   
    {  
        PrintStream ps = null;  
        try  
        {  
            //獲取系統信息:  
            Properties pop = System.getProperties();  
            //創建輸出流對象,將輸出流中數據存入指定文件中  
            ps = new PrintStream("systeminfo.txt");  
            //將屬性列表輸出到指定的輸出流  
            pop.list(ps);  
        }  
        catch (Exception e)  
        {  
            throw new RuntimeException("獲取系統信息失敗。");  
        }  
    }  
}

五、File類

1、概述:

1、File類:文件和目錄路徑的抽象表現形式

2、特點:
1)用來將文件或文件夾封裝成對象
2)方便于對文件與文件夾的屬性信息進行操作
3)File對象可以作為多數傳遞給流的構造函數

2、創建File對象:

方式一:

File f1 = new File("a.txt");

---> 將a.txt封裝成對象,可將已有的和未出現的文件或文件夾封裝成對象

方式二:

File f2 = new File("c:\\abc","b.txt");

---> 分別指定文件夾和文件。好處:降低了兩者的關聯程度,

方式三:

File d = new File("c:\\abc");     File f3 = new File(d,"c.txt");

---> 可以傳入父目錄和文件名。
目錄分隔符:調用File.separator,相當于是反斜杠 \

3、File類常見方法:

1、創建:

boolean createNewFile()  //在指定位置創建文件,若文件存在,則返回true,與輸出流不同,輸出流對象已建立就創建文件,如果存在,就會被覆蓋
boolean mkdir()  //創建文件夾,只能創建一級目錄
boolean mkdirs()  //創建多級文件夾。

2、刪除:

boolean delete()  //刪除文件。文件存在,返回true;文件不存在或者正在被執行,返回false
void deleteOnExit() //在程序結束時刪除文件

3、判斷:

boolean canExecute()   //當前文件是否能被執行
boolean exists()  //當前文件是否存在
boolean isFile()       //當前文件是否是文件
boolean isDirectory()  //當前文件是否是文件夾(目錄);注意:在判斷文件對象是否是文件或目錄是們必須要判斷該文件對象封裝的內容是否存在,通過exists判斷
boolean isHidden()   //當前文件是否是隱藏文件
boolean isAbsolute()   //測試此抽象路徑名是否為絕對路徑名

4、獲取信息:

String getName()     //獲取文件名
String getPath()     //獲取文件的相對路徑(即創建的對象傳入的參數是什么就獲取到什么)
String getParent()   //獲取父目錄,該方法返回絕對路徑中的父目錄,獲取相對路徑,返回null, 如果相對路徑中有上一級目錄,則返回的即為該目錄
String getAbsolutePath()  //獲取絕對路徑
long length()   //返回文件的大小
long lastModified()  //返回上次修改的時間
static File[] listRoots() //獲取文件的系統根,即各個盤符
String[] list()     //列出當前目錄所有文件,包含隱藏文件。注:調用了list方法的file對象,必須是封裝了一個目錄,且該目錄必須存在。
boolean renameTo(File dest)  //對文件重命名為dest

5、列出及過濾文件:

String[] list()   //列出當前目錄所有文件,包含隱藏文件,調用list方法的file對象,必須是封裝了一個目錄,且該目錄必須存在。
File[] list(FilenameFilter filter)  //FilenameFilter:文件名過濾器,是一個接口,其中包含一個方法,accept(File dir,String name),返回的是boolean型,對不符合條件的文件過濾掉。
File[] listFiles()  //獲取當前文件夾下的文件和文件夾,返回類型為File數組
ListFiles(FilenameFilter filter)   //同list,是對獲取的 當前文件夾下的文件和文件夾的 文件名過濾器。

示例:

import java.io.*;  
class FileMethod  
{  
    public static void sop(Object obj)  
    {  
        System.out.println(obj);  
    }  
    public static void main(String[] args) throws IOException  
    {  
  
        //methodCreate();  
        //methodDel();  
        methodList();  
    }  
    public static void methodCreate()throws IOException  
    {  
        File f = new File("file.txt");  
        boolean b = f.createNewFile();  
        sop("create:" + b);  
        File dir = new File("abc");  
        File dir2 = new File("ab.txt");  
        b = dir2.mkdir();  
        sop("mkdir2:" + b);  
        b = dir.mkdir();  
        sop("midir:" + b);  
        File dirs = new File("abc\\aa\\bb\\cc");  
        b = dirs.mkdirs();  
        sop("mkdirs:" + b);  
    }  
    public static void methodDel()throws IOException  
    {  
        File f = new File("file.txt");  
        boolean b = f.delete();  
        sop("delete:" + b);  
        File f2 = new File("Demo.txt");  
        f2.createNewFile();  
        f2.deleteOnExit();//為避免由于在執行后面的程序發生異常,這里如果創建了一個臨時文件,需要在程序結束時刪除。  
    }  
  
    public static void methodList()throws IOException  
    {  
        File f = new File("D:\\File");  
        String[] arr =  f.list(new FilenameFilter(){  
            public boolean accept(File dir,String name)  
            {  
                return name.endsWith(".bmp");  
            }  
        });  
        sop("len:" + arr.length);  
        for(String name : arr)  
        {  
            sop("bmp文件為:" + name);  
        }  
    }  
}

4、遞歸:

對于每次循環都是用同一個功能的函數,即函數自身調用自身,這種表現形式或手法,稱為遞歸。

注意:
1、限定條件,是作為結束循環用的,否則是死循環
2、注意遞歸的次數,盡量避免內存溢出。因為每次調用自身的時候都會先執行下一次調用自己的發那個發,所以會不斷在棧內存中開辟新空間,次數過多,會導致內存溢出。

舉例一:
列出指定目錄下文件或文件夾,包含子目錄,即列出指定目錄下所有內容
分析:
因為目錄中還有目錄,只有使用同一個列出目錄功能的函數完成即可,在列出過程中出現的還是目錄的話,還可以再調用本功能,這就是利用遞歸原理。

import java.io.*;  
class FileBack  
{  
    public static void main(String[] args)   
    {  
        File f = new File("D:\\File\\f");  
        backShowName(f);  
    }  
    //獲取文件及文件夾名  
    public static void backShowName(File dir)  
    {  
        //每次調用前,打印目錄名  
        System.out.println(dir.getName());  
        //將文件存入File數組  
        File[] files = dir.listFiles();  
        for(int i=0; i<files.length;i++)  
        {  
            //如果是文件夾,調用自身  
            if(files[i].isDirectory())  
                backShowName(files[i]);  
            else  
                System.out.println(files[i].getName());  
        }  
    }  
}

舉例二:
刪除一個帶內容的目錄:
刪除原理:
在window中,刪除目錄是從里往外刪除的,同樣使用遞歸

import java.io.*;  
class FileBack  
{  
    public static void main(String[] args)   
    {  
        File f = new File("D:\\File\\f");  
        removeDirs(f);  
    }  
    //刪除文件夾所有內容  
    public static void removeDirs(File f)  
    {  
        //將文件存入File數組  
        File[] files = f.listFiles();  
        for (int i=0;i<files.length;i++)  
        {  
            //如果是文件夾,調用自身  
            if(files[i].isDirectory())  
                removeDirs(files[i]);  
            else  
                System.out.println(files[i].getName() + ":files:" + files[i].delete());  
        }  
        System.out.println(f.getName() + ":-dir-:" + f.delete());  
    }  
}

舉例三:
將一指定目錄中的java文件的絕對路徑,存到一個文本文件中,建立一個java文件列表文件
思路:
1、對指定目錄進行遞歸
2、獲取遞歸過程所有的java文件的路徑
3、將這些路徑存儲到集合中
4、將集合中的數據寫到一個文件中

import java.util.*;  
import java.io.*;  
class WriterToFiles  
{  
    public static void main(String[] args)   
    {  
        File f = new File("D:\\JAVA");  
        List<File> list = new ArrayList<File>();  
        File dir = new File(f,"javalist.txt");  
        filesToList(f,list);  
        writeToFile(list,dir.toString());  
  
    }  
    //對指定目錄進行遞歸  
    public static void filesToList(File f,List<File> list)  
    {  
        File[] files = f.listFiles();  
        for (int i=0;i<files.length;i++)  
        {  
            //如果是文件夾,再調用自身  
            if(files[i].isDirectory())  
                filesToList(files[i],list);  
            else  
            {  
                //獲取遞歸過程所有的java文件的路徑  
                if(files[i].getName().endsWith(".java"))  
                    //將這些路徑存儲到集合中  
                    list.add(files[i]);  
            }  
        }  
    }  
    //將集合中的數據寫到一個文件中  
    public static void writeToFile(List<File> list,String javaListFile)  
    {  
        BufferedWriter bufw = null;  
        try  
        {  
            bufw = new BufferedWriter(new FileWriter(javaListFile));  
            //遍歷集合,獲取文件路徑,并寫入文件中  
            for(File f : list)  
            {  
                String path = f.getAbsolutePath();  
                bufw.write(path);  
                bufw.newLine();  
                bufw.flush();  
            }  
        }  
        catch (IOException e)  
        {  
            throw new RuntimeException("文件路徑獲取失敗");  
        }  
        //最終關閉寫入流資源  
        finally  
        {  
            try  
            {  
                if(bufw!=null)  
                bufw.close();  
            }  
            catch (IOException e)  
            {  
                throw new RuntimeException("文件路徑獲取失敗");  
            }  
        }  
    }  
}

六、Properties 類

1、概述:

1、Properties是Hashtable的子類,它具備Map集合的特點,而且它里面還有存儲的鍵值對,都是字符串,無泛型定義。是集合中和IO技術想結合的集合容器。

2、特點:
1)可用于鍵值對形式的配置文件
2)在加載時,需要數據有固定的格式,常用的是:鍵=值

2、特有方法:

1、設置和獲取元素:

Object setProperty(String key,String value)  //調用Hashtable的方法put
String getProperty(String key)    //指定key搜索value
Set<String> stringPropertyName()   //返回屬性列表的鍵集,存入Set集合
void load(InputStream ism)  //從輸入字符流中讀取屬性列表
void load(Reader reader)  //從輸入字符流中讀取屬性列表

load方法舉例:

//load方法  
public static void loadMthod()throws IOException  
{  
    Properties pop =new Properties();  
    FileInputStream fis = new FileInputStream("info.txt");  
    //將流中的數據加載進集合  
    pop.load(fis);  
      
    pop.setProperty("zz","25");  
    pop.setProperty("ww","24");  
    FileOutputStream fos = new FileOutputStream("info.txt");  
    pop.store(fos,"hehe");  
    pop.list(System.out);  
    fis.close();  
    fos.close();  
  
}

舉例:
如何將六種的數據存儲到集合中?
將文本文件中的鍵值數據存到集合中:
1、用一個流和文件關聯
2、讀取一行數據,將改行數據用“=”切割
3、將等號左邊作為鍵,右邊作為值,存入到Properties集合即可

//將流中的數據存儲到集合中  
public static void method()throws IOException  
{  
    BufferedReader bufr = null;  
    try  
    {  
        Properties pop = new Properties();  
        bufr = new BufferedReader(new FileReader("info.txt"));  
        String line = null;  
        while((line=bufr.readLine())!=null)  
        {  
            String[] arr = line.split("=");  
            pop.setProperty(arr[0],arr[1]);  
        }  
        System.out.println(pop);  
    }  
    catch (IOException e)  
    {  
        throw new RuntimeException("文件操作失敗");  
    }  
    finally  
    {  
        try  
        {  
            if(bufr!=null)  
                bufr.close();  
        }  
        catch (IOException e)  
        {  
            throw new RuntimeException("關閉流資源操作失敗");  
        }  
    }         
}

示例:用于記錄應用程序運行次數
如果使用次數已到,那么給出注冊提示需要使用計數器,但是在程序結束后,會在內存中消失,此時就需要將其存入到文件中,所以需要一個配置文件,用于記錄該軟件使用的次數。便于閱讀和操作數據

鍵值對數據 ---> Map集合;
數據以文件形式存儲 ---> IO技術。
--->Map+IO=Properties

import java.util.*;  
import java.io.*;  
class RunCount  
{  
    public static void main(String [] args)throws IOException  
    {  
        //創建一個Properties對象,集合和io的結合  
        Properties pop = new Properties();  
        //創建一個文件對象,用于操作文件  
        File file = new File("count.ini");  
        //先判斷文件是否存在,如果不存在就創建一個  
        if(!file.exists())  
            file.createNewFile();  
  
        //創建讀取流對象,讀取文件中的信息  
        FileInputStream fis = new FileInputStream(file);  
        //將流中的文件信息存入集合中  
        pop.load(fis);  
          
        //定義計數器  
        int count = 0;  
        //獲取文件中鍵所對應的值  
        String value = pop.getProperty("time");  
        //判斷值是否為null,不為空就將值傳給計數器  
        if(value!=null)  
        {  
            count = Integer.parseInt(value);  
            //判斷計數器是否為到達次數  
            if(count>=5)  
            {  
                System.out.println("次數已到,請注冊");  
                return ;  
            }  
        }  
        count++;  
        //將獲得的鍵值設置后存入集合中  
        pop.setProperty("time",count+"");  
        FileOutputStream fos = new FileOutputStream(file);  
        pop.store(fos,"");  
        fos.close();  
        fis.close();  
    }  
}

七、打印流

1、概述:

1、打印流包括:PrintStream和PrintWriter

2、該流提供了打印方法,可將各種類型的數據都原樣打印

2、字節打印流:PrintStream

構造方法中可接收的參數類型:

1、file對象。File

2、字符串路徑:String

3、字符輸出流:OutputStream

3、字符串打印流:PrintWriter

構造方法中可接受的參數類型

1、file對象:File

2、字符串路徑:String

3、字節輸出流:OutputStream

4、字符輸出流:Writer

舉例:

import java.io.*;  
  
class PrintDemo  
{  
    public static void main(String[] args) throws IOException  
    {  
        //鍵盤錄入,創建讀取流對象  
        BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));  
        //使用打印流,將文件輸出  
        //輸出到屏幕  
        PrintWriter out = new PrintWriter(new FileWriter("a.txt"),true);  
        String line = null;  
        while((line=bufr.readLine())!=null)  
        {  
            if("over".equals(line))  
                break;  
            out.println(line.toUpperCase());  
            //out.flush();  
        }  
        bufr.close();  
        out.close();  
    }  
}

八、合并流

1、概述:

1、SequenceInputStream可以將多個流連接成一個源

2、構造函數:

SequenceInputStream(Enumeration<? extends FileInputStream> e)

2、如何合并多個文件:

1、創建集合,并將流對象添加進集合

2、創建Enumeration對象,將集合元素加入。

3、創建SequenceInputStream對象,合并流對象

4、創建寫入流對象,FileOutputStream,將數據寫入流資源

5、定義數組,將讀取流數據存入數組,并將數組中元素寫入文件中。

舉例:
假設有三個文件,將三者合并到一個新文件中

//合并流對象  
public static void sequenceFile()throws IOException  
{  
    FileOutputStream fos = null;  
    SequenceInputStream sis = null;  
    try  
    {  
        //創建集合,存儲多個文件  
        ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();  
        for(int i=1;i<=3;i++)  
        {  
            al.add(new FileInputStream(i+".part"));  
        }  
        //匿名內部類訪問局部變量要final  
        final Iterator<FileInputStream> it = al.iterator();  
        //創建Enumeration匿名對象  
        Enumeration<FileInputStream> en = new Enumeration<FileInputStream>()  
        {  
            public boolean hasMoreElements()  
            {  
                return it.hasNext();  
            }  
            public FileInputStream nextElement()  
            {  
                return it.next();  
            }  
        };  
        //合并流對象,將集合元素加入。  
        sis = new SequenceInputStream(en);  
        //創建寫入流對象,FileOutputStream  
        fos = new FileOutputStream("7.bmp");  
        byte[] b = new byte[1024*1024];  
        int len = 0;  
        //循環,將數據寫入流資源  
        while((len=sis.read(b))!=-1)  
        {  
            fos.write(b);  
        }  
  
    }  
    catch (IOException e)  
    {  
        throw new RuntimeException("文件操作失敗");  
    }  
    //關閉流資源  
    finally  
    {  
        try  
        {  
            if(fos!=null)  
                fos.close();  
        }  
        catch (IOException e)  
        {  
            throw new RuntimeException("關閉流資源操作失敗");  
        }  
        try  
        {  
            if(sis!=null)  
                sis.close();  
        }  
        catch (IOException e)  
        {  
            throw new RuntimeException("關閉流資源操作失敗");  
        }  
    }         
}

3、切割流資源:

1、先關聯文件FileInputStream

2、定義寫入流變量:FileOutputStream

3、創建數組,并定義切割所需的大小|

4、循環讀寫數據,并每次創建一個新寫入流,創建完后并寫入文件中

5、關閉流資源

    //切割流對象  
    public static void splitFile()throws IOException  
    {  
        //創建全局變量  
        FileInputStream fis = null;  
        FileOutputStream fos = null;  
        try  
        {  
            //創建文件讀取流  
            fis = new FileInputStream("0.bmp");  
            //創建數組  
            byte[] b = new byte[1024*1024];  
            int len = 0;  
            //計數器  
            int count = 1;  
            //循環寫入數據  
            while((len=fis.read(b))!=-1)  
            {  
                //每次創建一個新寫入流,寫入后關閉流資源  
                fos = new FileOutputStream((count++)+".part");  
                fos.write(b,0,len);  
                fos.close();  
            }  
        }  
        catch (IOException e)  
        {  
                throw new RuntimeException("關閉流資源操作失敗");  
        }  
        //關閉流資源  
        finally  
        {  
            try  
            {  
                if(fis!=null)  
                    fis.close();  
            }  
            catch (IOException e)  
            {  
                throw new RuntimeException("關閉流資源操作失敗");  
            }  
        }  
          
    }  
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,238評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,430評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,134評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,893評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,653評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,136評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,212評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,372評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,888評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,738評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,482評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,179評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,588評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,829評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,610評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,916評論 2 372

推薦閱讀更多精彩內容

  • tags:io categories:總結 date: 2017-03-28 22:49:50 不僅僅在JAVA領...
    行徑行閱讀 2,189評論 0 3
  • 一、對象序列化 ObjectOutputStram和ObjectInputStream 1、概述: 將堆內存中的對...
    玉圣閱讀 295評論 0 0
  • 在經過一次沒有準備的面試后,發現自己雖然寫了兩年的android代碼,基礎知識卻忘的差不多了。這是程序員的大忌,沒...
    猿來如癡閱讀 2,863評論 3 10
  • 凄風冷雨中,小朱背一背包,拖著一舊箱子下了火車,單薄的身影迅速被廣州燈火輝煌的夜景包裹。 街道四通八達,到處都是叉...
    下吧毛毛雨閱讀 350評論 0 0
  • 此刻是晚上,當我10:42回到家,放下包,立刻沖到桌前,來敲完這些文字。嗯,這是一場與時間的賽跑。也或者說,這是一...
    王小落閱讀 2,545評論 0 1