IO_體系_總結

IO流總結

IOInput Output)流

l IO流用來處理設備之間的數據傳輸

l Java對數據的操作是通過流的方式

l Java用於操作流的對象都在IO包中

l 流按操作數據分爲兩種:字節流與字符流。

l 流按流向分爲:輸入流,輸出流。

輸入流和輸出流的流向的理解?

流就是處理數據的一種方式或者一種手段,或者理解爲一種數據流。

從硬盤已有的數據讀取出來放內存裏面的這個過程就是輸入流。

把內存中的數據存儲到硬盤中的這個過程就是輸出流。

簡單理解就是:以內存爲中心。

什麼時候使用流對象?

操作設備上的數據或操作文件的時候可以使用。

字符流

字符流的抽象基類:Reader , Writer

字符流的理解,由來和作用?

由於很多國家的文字融入進來,比如說中文在編碼表中默認佔2個字節。而爲了按照文字的單位來處理,所以出現了字符流。

由來:早期的字節流+編碼表,爲了更便於操作文字數據。

作用:爲了處理文字數據。

複製文件的原理和代碼。

原理:

首先用一個讀取流對象和一個文件進行關聯,然後用一個寫入流對象作爲目地的,

爲了把讀取流中的文件傳輸到目的地流對象中,我們就提供了一個字符數組,

爲了關聯這個數組,所以讀取流對象有一個read()方法與這個字符數組進行關聯,

同理,寫入流對象也有一個write()方法與這個字符數組進行關聯,

這樣2個流對象就相連接了,而這個字符數組就相當於一箇中轉站。

import java.io.FileReader;

import java.io.FileWriter;

import java.io.IOException;

/*

 * 對文本文件進行復制。將c盤的文件複製到d盤中。

 * 原理:其實就是一個最簡單的讀寫過程。

 * c盤源,讀取數據,並將讀到的數據,寫入到目的d盤中。

 */

public class CopyTextFileTest {

public static void main(String[] args) {

FileReader fr = null;

FileWriter fw = null;

try {

//1,創建一個字符讀取流讀取與源數據相關聯。

fr = new FileReader("demo.txt");

//2,創建一個存儲數據的目的地。

fw = new FileWriter("copyDemo.txt");

//3,創建一個字符數組將讀取流對象和寫入流對象相連接。

char[] buf = new char[1024];

//4,每次讀取的長度不一樣,所以定義一個變量.

int len = 0;

//5,用循環讀取文件中的數據

while((len= fr.read(buf)) != -1) //判斷是否讀取完沒

fw.write(buf,0,len); //爲了只讀取有效的數據

} catch (Exception e) {

}finally{

try {

fr.close();

} catch (IOException e) {

e.printStackTrace();

}

try {

fw.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

讀取字符流對象的兩種方式

第一種讀取方式  一次讀一個字符

  //1,創建一個文件讀取流對象,和指定名稱的文件相關聯

        //2,要保證該文件時已經存在的。如果不存在,會發生異常。FileNotFoundException

        FileReader fr = new FileReader("Demo.txt");

        //3,調用讀取流的方法,read方法一次讀一個字符,而且會自動往下讀。

        int line=0;      //read返回時int型的數,即返回的是字符的ascII表對應的數字

        while ((line=fr.read())!=-1)

        {

             sop((char)ch);

        }

    第二種讀取方式 

        //1,創建一個文件讀取流對象,和指定名稱的文件相關聯

        //2,要保證該文件時已經存在的。如果不存在,會發生異常。FileNotFoundException

        FileReader fr = new FileReader("Demo.txt");   //Demo.txt中的數據讀到控制檯

        //3,定義一個字符數組,用於存儲獨到的字符該read(char[]) //返回的是讀到字符的個數

        char[] buf = new char[1024];

        int len=0;

        while((len=fr.read(buf))!=-1)  //把讀到的字符暫時存到buf數組中

        {

           sop("num="+num+"...."+new String(buf,0,len));

        }

 

緩衝區的基本思想?提高效率的原理。

緩衝區的基本思想就是對要處理的數據進行臨時存儲。譬如購物車以及籃子。

原理:

減少頻繁的操作。

給讀取流對象和寫入流對象提供中轉站。

相對於來回跑的麻煩,利用緩衝區的容量,可以一邊先存儲,滿了後再寫入的方式,這樣就提高了效率。

BufferedReader BufferedWriter.高效的體現?

內部將數組進行封裝。

變成對象後,方便於對緩衝區的操作。提高效率。

並提供了對文本便捷操作的方法。

readLine

newLine-----只有BufferedWriter裏有

自定義緩衝區,MyBufferedReader

import java.io.IOException;

import java.io.Reader;

/*

 * 模擬一個緩衝區

 * 基於已有的緩衝區思想,我們可以從源讀取用read方法。

 * 我們的緩衝區,應該是一個更高效的read讀取方法。

 */

public class MyBufferTest extends Reader{

private Reader r;

private char[] buf = new char[1024];

private int count = 0,pos = 0;

public MyBufferTest(Reader r){

this.r = r;

}

/**

 * 一次從緩衝區中取一個

 * @return 返回一個緩衝區中的字符

 * @throws IOException 

 */

public int myRead() throws IOException {

//1,首先判斷緩衝區中是否有數據,如果沒有就從源中去拿。

if(count == 0){

count = r.read(buf);

pos = 0;

}

//2,當緩衝區中沒數據了且源中也沒有數據時,count自減1小於0時就返回-1結束.

if(count < 0)

return -1;

//3,如果以上都不滿足,那麼從緩衝區中寫入一個字符到新的文件中。

char ch = buf[pos];

pos++;

count--;

return ch;

}

/**

 * 按照文本特點,提供一個特有的操作文本的方法。

 * 一次讀取一行文本,只要是到行結束符之前的文本即可。

 * @return 返回讀取到的一行文本

 * @throws IOException

 * 原理:

 *  就是從緩衝區中取出數據,並存儲到一個臨時容器中。

 *  如果取到了回車符,就將臨時容器中的數據轉成字符串返回。

 */

public String myReadLine() throws IOException{

//1,定義一個臨時容器,進行臨時存儲

StringBuilder sb = new StringBuilder();

//2,定義一個變量,接收讀取到的字符,也就是轉成ask碼錶後的一個int型數字

int ch = 0;

while((ch = myRead()) != -1){

//3,當讀取到\r時,直接跳出本次循環

if(ch == '\r')

continue;

//4,當讀取到\n時,直接跳出當前循環

if(ch == '\n')

return sb.toString();

//5,當都沒有讀取到時,就將這些數據存儲到臨時容器中。

sb.append((char)ch);

}

//6,當臨時容器中的長度不等於0時,就輸出字符。

if(sb.length() != 0)

return sb.toString();

return null;

}

@Override

public void close() throws IOException {

}

@Override

public int read(char[] arg0, int arg1, int arg2) throws IOException {

return 0;

}

}

readLine方法的原理。

就是從緩衝區中獲取數據,並進行臨時存儲,知道讀取到了換行符,

將臨時存儲的數據轉成字符串返回。

它對於操作文本是畢竟方便,可以完成一行一行的讀取文本。

裝飾設計模式,以及和繼承的區別?

對原有類進行了功能的改變,增強。

區別:

1

繼承在對對象進行增強時,採用的是子類覆蓋父類中的寫方法,

且這些子類使用的功能的原理都一樣,這樣就顯得很臃腫。

BufferWriter的出現避免了繼承體系關係的臃腫,比繼承更爲靈活。

2

在爲了增強功能的情況下,相較於繼承,BufferWriter這種方式解決起來更爲方便。

使用字符流可以複製圖片嗎?爲什麼?

不能,因爲字符流就是字節流+編碼表,而用字符流去複製圖片時,字符流會默認將圖片的字節碼格式進行編碼,

所以會導致複製後的圖片與原圖片可能不一致。

字符流只能複製文本

字符流繼承體系簡圖

字節流的抽象基類:InputStream OutputStream

字節流繼承體系簡圖

轉換流InputStreamReader,OutputStreamWriter

轉換流的由來?

爲了方便於字符流與字節流進行轉換,也就是建立一個橋樑。

建立橋樑後,它就將字節流和編碼表進行了封裝,實現了對字符的便捷操作。

另外也爲了方便字符流與字節流之間的操作。

轉換流的應用?

字節流中的數據都是字符時,轉成字符流操作更高效。

轉換流的兩個橋樑都是從哪裏到哪裏?

首先將文件通過InputStreamReader的方式將字節數據轉成字符,爲了高效,將其先存儲到緩衝區中。

然後通過OutputStreamWriter將緩衝區中的字符數據轉成字節,最後輸出。

轉換流的另一個功能,編碼的體現?

對操作的文本文件使用指定編碼表進行編碼解碼的操作。

轉換流的子類和轉換流的區別?

區別:

1

轉換流:字節流+編碼表

轉換流的子類:FileReader&FileWriter:字節流+本地默認碼錶(GBK)。

2

轉換流可以指定任意碼錶。

而轉換流子類需要構造一個其父類的對象。

標準輸入輸出流

l System類中的字段:in,out。

l 它們各代表了系統標準的輸入和輸出設備。

l 默認輸入設備是鍵盤,輸出設備是顯示器。

l System.in的類型是InputStream.

l System.out的類型是PrintStream是OutputStream的子類FilterOutputStream 的子類.

什麼是標準輸入輸出流?

:獲取鍵盤錄入數據,然後將數據流向顯示器,那麼顯示器就是目的地。

通過System類的setInsetOut方法對默認設備進行改變。

• System.setIn(new FileInputStream(“1.txt”));//將源改成文件1.txt

• System.setOut(new PrintStream(“2.txt”));//將目的改成文件2.txt

因爲是字節流處理的是文本數據,可以轉換成字符流,操作更方便。

BfferedReader bufr = 

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

BufferedWriter bufw = 

new BufferedWriter(new OutputStreamWriter(System.out));

流的基本應用小節

流是用來處理數據的。

處理數據時,一定要先明確數據源,與數據目的地(數據匯)。

數據源可以是文件,可以是鍵盤。

數據目的地可以是文件、顯示器或者其他設備。

而流只是在幫助數據進行傳輸,並對傳輸的數據進行處理,比如過濾處理.轉換處理等

IO流的操作規律總結:

1,明確體系:

數據源:InputStream Reader

數據匯:OutputStreamWriter

2,明確數據:因爲數據分兩種:字節,字符。

數據源:是否是純文本數據呢?

是:Reader

否:InputStream

數據匯:

是:Writer

否:OutputStream

到這裏就可以明確具體要使用哪一個體系了。

剩下的就是要明確使用這個體系中的哪個對象。

3,明確設備:

數據源:

鍵盤:System.in

硬盤:FileXXX

內存:數組。

網絡:socket

數據匯:

控制檯:System.out

硬盤:FileXXX

內存:數組

網絡:socket

4,明確額外功能:

1,需要轉換?是,使用轉換流。InputStreamReader OutputStreamWriter

2,需要高效?是,使用緩衝區。Buffered

3,需要其他?

1, 複製一個文本文件。

1,明確體系:

源:InputStream Reader

目的:OutputStream Writer

2,明確數據:

源:是純文本嗎?是 Reader

目的;是純文本嗎?是 Writer

3,明確設備:

源:硬盤上的一個文件。 FileReader

目的:硬盤上的一個文件。FileWriter

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

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

4,需要額外功能嗎?

需要,高效,使用buffer

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

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

2, 讀取鍵盤錄入,將數據存儲到一個文件中。

1,明確體系:

源:InputStream ,Reader

目的:OutputStream ,Writer

2,明確數據:

源:是純文本嗎?是 Reader

目的;是純文本嗎?是 Writer

3,明確設備:

源:鍵盤,System.in

目的:硬盤,FileWriter

InputStream in = System.in;

FileWriter fw = new FileWriter("a.txt");

4,需要額外功能嗎?

需要,因爲源明確的體系時Reader。可是源的設備是System.in。

所以爲了方便於操作文本數據,將源轉成字符流。需要轉換流。InputStreamReader

InputStreamReader isr = new InputStreamReader(System.in);

FileWriter fw  = new FileWriter("a.txt");

需要高效不?需要。Buffer

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

BufferedWriter bufw = new BufferedWriter(new FileWriter("a.txt"));

3, 讀取一個文本文件,將數據展現在控制檯上。

1,明確體系:

源:InputStream ,Reader

目的:OutputStream ,Writer

2,明確數據:

源:是純文本嗎?是 Reader

目的;是純文本嗎?是 Writer

3,明確設備:

源:硬盤文件,FileReader。

目的:控制檯:System.out。

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

OutputStream out = System.out;

4,需要額外功能?

因爲源是文本數據,確定是Writer體系。所以爲了方便操作字符數據,

需要使用字符流,但是目的又是一個字節輸出流。

需要一個轉換流,OutputStreamWriter

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

OutputStreamWriter osw = new OutputStreamWriter(System.out);

需要高效嗎?需要。

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

BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));

4, 讀取鍵盤錄入,將數據展現在控制檯上。

1,明確體系:

源:InputStream ,Reader

目的:OutputStream ,Writer

2,明確數據:

源:是純文本嗎?是 Reader

目的;是純文本嗎?是 Writer

3,明確設備:

源:鍵盤:System.in

目的:控制檯:System.out

InputStream in = System.in;

OutputStream out = System.out;

4,需要額外功能嗎?

因爲處理的數據是文本數據,同時確定是字符流體系。

爲方便操作字符數據的可以將源和目的都轉成字符流。使用轉換流。

爲了提高效率,使用Buffer

BufferedReader bufr  =new BufferedReader(new InputStreamReader(Systme.in));

BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));

5, 讀取一個文本文件,將文件按照指定的編碼表UTF-8進行存儲,保存到另一個文件中。

1,明確體系:

源:InputStream ,Reader

目的:OutputStream ,Writer

2,明確數據:

源:是純文本嗎?是 Reader

目的;是純文本嗎?是 Writer

3,明確設備:

源:硬盤:FileReader.

目的:硬盤:FileWriter

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

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

4,額外功能:

注意:目的中雖然是一個文件,但是需要指定編碼表。

而直接操作文本文件的FileWriter本身內置的是本地默認碼錶。無法明確具體指定碼錶。

這時就需要轉換功能。OutputStreamWriter,而這個轉換流需要接受一個字節輸出流,而且

對應的目的是一個文件。這時就使用字節輸出流中的操作文件的流對象。FileOutputStream.

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

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

需要高效嗎?

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

BufferedWriter bufw = 

new BufferedWriter(new OutputStreamWriter(new FileOutputStream("b.txt"),"UTF-8"));

File

File類的作用?

1,用來將文件或者文件夾封裝成對象

2,方便對文件與文件夾的屬性信息進行操作。

3File對象可以作爲參數傳遞給流的構造函數。

File對象基本使用。瞭解創建,刪除,獲取,判斷等。

創建:

boolean createNewFile():創建一個新的空文件,不過該文件已存在,就不會創建

boolean mkdir():創建指定的目錄

boolean mkdirs():創建指定的目錄已經父目錄

刪除:

boolean delete():刪除文件或目錄

void deleteOnExit():虛擬機結束時,會自動刪除指定的文件或目錄

獲取:

String getAbsolutePath():絕對路徑

String getPath():相對路徑

String getParent():返回此路徑名父目錄的路徑,如果沒有則返回null

String getName():返回此路徑名錶示的文件或目錄的名稱

long length():返回此路徑名錶示的文件的長度

long lastModified():返回此文件最後一次被修改的時間

判斷:

boolean exists():判斷此路徑名錶示的文件或目錄是否存在

boolean isFile():判斷此路徑名錶示的文件或目錄是否是一個標準的文件

boolean isDirectory():判斷此路徑名錶示的文件或目錄是否是一個目錄

File對象的過濾器。

實現FilenameFilter接口,然後覆蓋其accept方法並指定需要過濾的擴展名就哦了。實現此接口的類實例可用於過濾器文件名。

boolean accpet(File dir, String name):當且僅當該名稱應該包含在文件列表中時返回 true;否則返回 false

遞歸

什麼是遞歸?

函數自己調用自己。

遞歸的使用和注意事項,並舉例。

遞歸就是函數自身直接或間接調用自身。

當一個功能被重複使用,而且使用過程中,參數運算的數據不斷的在發生着變化。

這時可以使用遞歸這種手法來解決問題。

注意事項:

1,必須要定義條件,否則會出現棧內存溢出。

2,要控制遞歸的次數。

例如:當我們傳遞一個參數超出了棧內存的範圍,且這些數據並沒有出棧,所以就會出問題。

遞歸詳解圖

Properties

Properties類的作用?

主要用於存儲鍵值的map集合中Hashtable的子類。

操作配置文件最方便的對象。

Properties的特點?

1,它沒有泛型。

2,它裏面的鍵和值都是固定的類型,字符串。

3,它自己有特有的存儲和取出的動作。

4,它有和IO流相關聯的方法。

5,它經常用於簡單配置文件的解析。

Properties常見方法的使用?

void list(PrintWriter out):列出屬性值

void store(OutputStream out, String comments):將集合中的鍵值信息進行持久化存儲

void load(Reader reader) :從持久化設備上將指定格式的鍵值信息加載到Properties集合中

Properties&XML應用實例:

IO中的其他功能流對象:

1,打印流:

PrintStream:

特點:

1,構造函數可接收File對象,字符串路徑,字節輸出流。意味着打印目的可以有很多。

2,該對象具備特有的方法 打印方法print println,可以任何類型的數據。

3,特有的print方法可以保持任意類型的數據表現形式的原樣性,將數據輸出到目的地。

而對於OutputStream父類中的write方法,是將數據的最低字節寫出去。

PrintWriter:

特點:

1,當操作的數據是字符時,可以選擇PrintWriter,比PrintStream要方便。

2,它的構造函數可以接收File對象,字符串路徑,字節輸出流,字符輸出流。

3,構造函數中,如果參數是輸出流,那麼可以通過指定另一個參數true完成自動刷新,該trueprintln方法有效。

什麼時候用?

當需要保證數據表現的原樣性時,就可以使用打印流的打印方法來完成,這樣更爲方便。

保證數據表現形式的原樣性的原理:其實就是將數據變成字符串,再進行寫入操作。

print(int) write(int)的區別?

print(int)方法可以保持任意類型的數據表現形式的原樣性,將數據輸出到目的地。

write(int)方法只是將數據的最低字節寫出去。

PrintWriter可以打印的目的地有什麼?

有字節和字符。

PrintWriter在什麼情況下可以自動刷新?

構造函數中,如果參數是輸出流,那麼可以通過指定另一個參數true完成自動刷新,該trueprintln方法有效。

PrintWriter對象的println自動刷新圖

注意,自動刷新功能只對字節流對象和字符流對象有效。

2,序列流:

SequenceInputStream:

特點:

1,將多個字節讀取流合併成一個讀取流,將多個源合併成一個源,操作起來更方便。

2,需要的枚舉接口可以通過Collections.enumeration(collection);

可以完成什麼功能?

可以將多個字節讀取流合併成一個讀取流,將多個源合併成一個源的功能。

文件切割的原理?

通過讀取流關聯源文件,然後讀取流將一個臨時的數據進行了一個臨時的緩衝,然後通過這個臨時的緩衝區將數據分散到不同的文件當中。

也就意味着一個讀取流對應着多個輸出流。

ArrayList集合如何獲取其對應的枚舉對象?

實例化一個Enumeration對象,實現其內部方法,並且其內部實現過程和迭代器的功能一樣,所以可以使用迭代的方法完美枚舉的功能。

3,對象序列化

ObjectInputStream 和 ObjectOutputStream :對象的序列化和反序列化

實現Serializable接口。

啓用其序列化功能的。

序列化是驗證序列化對象的。

必須添加默認ID號。

ID號的作用:可以對對象的聲明週期進行延長。

關鍵字transient

對象的序列化和反序列化,怎麼理解的?

理解:序列化是驗證序列化對象的。對象的持久化過程。

也就是將對象寫入設備的方式,當然,也可以完成多個對象才存儲,那麼每個對象都需要有一個順序。

Serializable接口有什麼用?

啓用其序列化功能的。

關於序列化中數字簽名的小細節

數字簽名不一致,也就是Seriable接口相關的類發生了改變,ID號發生了改變

非靜態數據不需要序列化,如何解決?

使用關鍵字transient

4,隨機訪問文件

RandomAccessFile:

• 隨機訪問文件,自身具備讀寫的方法。

• 通過skipBytes(int x),seek(int x)來達到隨機訪問。

特點:

1,既可以讀取,也可以寫入。

2,內部維護了一個大型的byte數組,通過對數組的操作完成讀取和寫入。

3,可以通過getFilePointer方法獲取指針的位置,還可以通過seek方法設置指針的位置。

4,該對象的內部應該封裝了字節輸入流和字節輸出流。

5,該對象只能操作文件。(侷限性)

完成隨機訪問的原理?

對數組不斷的操作。

通過seek方法操作指針,可以從這個數組中的任意位置上進行讀和寫。

可以完成對數據的修改,但是數據必須有規律。

5,管道流:

需要和多線程技術相結合的流對象。

特點:

1,可以將管道輸出流連接到管道輸入流通信管道。

2,需要和多線程技術相結合的流對象。

6,操作基本數據類型(額外功能的對象)

DataInputStream 和 DataOutputStream

特點:用於操作基本數據類型值的對象。

7,操作字節數組

ByteArrayInputStream 和 ByteArrayOutputStream

8,設備是內存的流對象。

操作字符數組

• CharArrayReader與CharArrayWrite

操作字符串

• StringReader 與 StringWriter

IO中的這些功能流對象在用流操作規律分析時,都在第四步,是否需要額外功能呢?

1,轉換嗎?

一是需要橋樑嗎?

2,高效嗎?

隨機訪問文件 RandomAccessFile

3,序列化嗎?

ObjectInputStream 和 ObjectOutputStream :對象的序列化和反序列化    Serializable接口

4,操作基本數據類型嗎?

DataInputStream 和  DataOutputStream

5, 需要保證數據原樣性?

printWriter

編碼表

編碼表的由來

l 計算機只能識別二進制數據,早期由來是電信號。

l 爲了方便應用計算機,讓它可以識別各個國家的文字。

l 就將各個國家的文字用數字來表示,並一一對應,形成一張表。

l 這就是編碼表。

字符編碼

字符流的出現爲了方便操作字符。

更重要是的加入了編碼轉換。

通過子類轉換流來完成。

• InputStreamReader

• OutputStreamWriter

在兩個對象進行構造的時候可以加入字符集。

OutputStreamWriter osw = new OutputStreamWriter(new FileWriter(“test.txt”));

常見的碼錶有哪些,都有什麼特點?

ASCll:美國標準信息交換碼

用一個字節的7位可以表示

ISO8859-1:拉丁碼錶。歐洲碼錶

用一個字節的8位表示

GB2312:中國的中文編碼表

GBK:中國的中文編碼表升級,融合了更多的中文文字字符號。

Unicode:國際標準碼,融合了多種文字。

所有文字都用兩個字節來表示,Java語言使用的就是unicode

UTF-8:最多用三個字節來表示一個字符

編解碼的原則:編對解錯,編錯。

編碼:字符串-->字節數組 可以理解爲將認識的變成不認識的就是編碼

解碼:字節數組-->字符串 可以理解爲將不認識的變成認識的就是解碼

編碼錯了,就解不出來了。

編碼對了,解錯了,有可能有救。

當涉及向服務端提交中文數據時,服務器是iso8859-1的碼錶,如何獲取正確的中文?

對亂碼進行iso8859-1編碼,在用gbk解碼即可。

聯通是怎麼回事?

聯通的GBK的編碼正好符合了UTF-8的編碼,所以就用了UTF-8的碼錶進行解碼,因此出現了亂碼。

也就是聯通的GBK編碼表和UTF-8重複了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章