Java基礎(14):IO流—理解I/0概念和掌握相關類的作用(附有操作程式碼)
在Java中如果要進行輸出和輸入操作,就需要使用到IO流,例如第一次寫的語句System.out.println("hello,world")就是一個典型的輸出流。IO流是Java的重點知識,除了要理解輸入與輸出的概念,還需要多次編寫程式碼才能更好的理解。IO體系中涉及到的類很多,但核心體系就是由File、 InputStream 、OutputStream、Reader、Writer和Serializable(介面)組成的,這些類會在下半部分的程式碼實踐中詳細說明。
I/O流基礎概念篇
按照流的方向可以分為輸入流(InputStream)與輸出流(OuputStream):
- 輸入流:只能讀取資料,不能寫入資料。
- 輸出流:只能寫入資料,不能讀取資料。
因為程式是執行在記憶體中,所以從記憶體的角度來理解輸入輸出概念,貼上一張圖。
但輸入和輸出是相對的,程式把資料寫入本地檔案,對於程式來說是輸出流,而本地檔案則是獲取了資料,對本地檔案來說就是輸入流。本地檔案寫入資料到Java程式中也是輸出流,對於Java程式來說就是輸入流。
所以需要從使用的角度來區分,讀取資料就是輸入流,寫入資料就是輸出流。
按照處理的資料單位可以分為位元組流和字元流:
- 位元組流:操作的資料單元是8位的位元組。以InputStream、OutputStream作為抽象基類。
- 字元流:操作的資料單元是字元。以Writer、Reader作為抽象基類。
- 位元組流可以處理所有資料檔案,如果處理的是純文字資料,就使用字元流。
按照流的作用可分為節點流和處理流:
節點流:程式直接與資料來源連線,和實際的輸入/輸出節點連線。
處理流:對節點流進行包裝,擴充套件原來的功能,通過處理流執行輸入/輸出操作。這涉及到裝飾者模式。
IO流中的資料來源有三類:
- 基於磁碟檔案:FileInputStream、FileOutputSteam、FileReader、FileWriter
- 基於記憶體:ByteArrayInputStream ByteArrayOutputStream(ps:位元組陣列都是在記憶體中產生的)
- 基於網路:SocketInputStream、SocketOutputStream(ps:網路通訊時傳輸資料)
處理流的作用和分類:
處理流可以隱藏底層裝置上節點流的差異,無需關心資料來源的來源,程式只需要通過處理流執行輸入/輸出操作。處理流是以一個存在的節點流作為構造引數的。通常情況下,都推薦使用處理流來完成輸入/輸出操作。
緩衝流:提供一個緩衝區,能夠提高輸入/輸出的執行效率,減少同節點的頻繁操作。
BufferedInputStream/BufferedOutputStream、BufferedReader/BufferWriter
轉換流:將位元組流轉成字元流。位元組流使用範圍廣,但字元流使用更方便。例如一個位元組流的資料來源是文字內容的話,轉成字元流來處理會更加方便。
InputStreamReader/OutputStreamWriter
列印輸出流:把指定內容列印輸出,根據構造引數中的節點流來決定輸出到何處。
PrintStream :列印輸出位元組資料,使用該類。
PrintWriter : 列印輸出文字資料,使用該類。
放上一張JavaI/O體系的常用類圖片,方便後面程式碼講解。
---------------------------------------------------------------------------------------------------
I/O流實踐
File類: 代表本地磁碟的檔案或是一個目錄路徑,File類只能對檔案本身進行操作,例如判斷是檔案還是目錄,判斷檔案是否存在,建立一個檔案或者資料夾等操作。但不能對檔案儲存的內容進行操作。File類中還有許多方法,這裡不一一概述。一些方法會在接下來的程式碼中使用到。想了解更多方法可以去檢視JDK文件。介紹完基礎概念之後,開始使用IO流來完成一些功能:
(一)使用位元組流讀取本地檔案內容:
//使用File物件來獲取資料來源,並讀取其中內容,將其輸出到標準控制檯
public static void getContent(File file) throws IOException {
//建立檔案輸入流,資料來源是file
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
byte[] buf = new byte[1024];//可以快取50位元組
int len = 0; //讀取後返回的長度
while((len = bis.read(buf)) != -1) { //長度為-1則讀取完畢,結束迴圈讀取
//列印快取區的內容,從0開始
System.out.println(new String(buf,0,len));
}
bis.close();
}
使用File類獲取資料來源,使用檔案位元組輸入流來讀取該資料來源。如果資料來源是純文字資料的話,使用字元流會更方便,而且效率更高。
(二)使用字元處理流讀取本地檔案內容:
public static void getContent(String path) throws IOException {
File f = new File(path);
//判斷f指向的資料來源是否存在
if(f.exists()) {
//判斷是不是檔案
if(f.isFile()) {
//不要向上轉型成Reader,readLine()是子類獨有方法
BufferedReader br = new BufferedReader(new FileReader(path));
String s = null;
while((s = br.readLine()) != null) { //readLine()每次讀取一行
System.out.println(s);
}
}
}
}
該方法比上一個增加了檔案判斷,提高了程式的健壯性。使用了BufferedReader處理流來處理純文字資料,比位元組流更加簡潔方便。如果處理的是純文字資料,強烈推薦字元處理流!!!(三)使用字元流寫入資料到指定檔案:
public static void main(String[] args) throws IOException {
//以標準輸入作為掃描來源
Scanner sc = new Scanner(System.in);
File f = new File("D:\\reviewIO\\WRITERTest.txt");
BufferedWriter bw = new BufferedWriter(new FileWriter(f));
if(!f.exists()) {
f.createNewFile();
}
while(true) {
String s = sc.nextLine();
bw.write(s);
bw.flush();
if(s.equals("結束") || s.equals("")) {
System.out.println("寫入資料結束!");
return;
}
}
}
上面程式使用字元流簡單地實現了寫入檔案的操作,位元組流也差不多,可以自行更改。關於Scanner類,該類是一個掃描類,以System.in作為資料來源。System.in是標準輸入(鍵盤輸入資料),這裡只是先了解一下。
(四)使用轉換流(InputStreamReader/OutputStreamWriter),對寫入資料進行改進:
public static void testConvert(File f) throws IOException {
if(!f.exists()) {
f.createNewFile();
}
//以System.in作為讀取的資料來源
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new FileWriter(f,true)); //允許新增內容,不會清除原來的資料來源內容。
String s = null;
while(!(s = br.readLine()).equals("")) {
bw.write(s);
bw.newLine();//空一行
}
bw.flush();
bw.close();
br.close();
}
因為System.in是一個InputStream物件,字元處理流無法直接使用,所以需要使用轉換流將位元組流轉成字元流。然後使用字元輸入處理流的readLine()每次讀取一行,使用newLine()完成換行。
注意點:通常使用IO流寫入檔案時,寫入的資料總會覆蓋原來的資料,這是因為檔案輸出流預設不允許追加內容,所以需要為FileOuputStream、FileWriter的構造引數boolean append 傳入true。 (五)使用位元組流完成檔案拷貝://位元組流實現檔案拷貝
public static String copyFile(String src, String dest) throws IOException, ClassNotFoundException {
File srcFile = new File(src);//原始檔資料來源
File desFile = new File(dest);//寫入到目標資料來源
//資料來源不存在
if(!srcFile.exists() || !desFile.exists()) {
throw new ClassNotFoundException("原始檔或者拷貝目標檔案地址不存在!");
}
//非檔案型別
if(!srcFile.isFile() || !desFile.isFile()) {
return "原始檔或者目標檔案不是檔案型別!";
}
InputStream is = null;
OutputStream os = null;
byte[] buf = new byte[1024];//快取區
int len = 0;//讀取長度
try {
is = new BufferedInputStream(new FileInputStream(srcFile));//讀取資料來源
os = new BufferedOutputStream(new FileOutputStream(desFile));//寫入到資料來源
while((len = is.read(buf)) != -1) { //讀取長度不為-1,繼續讀取
os.write(buf); //讀取內容之後馬上寫入目標資料來源
}
os.flush();//輸出
return "檔案拷貝成功!檢視拷貝檔案路徑:" + desFile.getPath();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(is != null)
is.close();
if(os != null)
os.close();
}
return "檔案拷貝失敗";
}
如果是對檔案進行復制,一般採用位元組流來完成,因為可以作用於一切型別資料。但如果操作的是純文字資料,應該使用字元流。
(六)使用列印流來完成寫入資料操作:
//輸出內容的檔案資料來源
File f = new File("D:\\reviewIO\\PW.java");
PrintWriter pw = new PrintWriter(f);
//把指定內容列印至資料來源中
pw.println("AAAAAAAAA");
pw.println("BBBBBBBBB");
pw.println("CCCCCCCCC");
pw.flush();
System.out.println("使用PrintWriter寫入資料完成");
System.out.println("==========讀取寫入的資料==========");
BufferedReader br = new BufferedReader(new FileReader(f));
String s = null;
StringBuilder sb = new StringBuilder();//一個可變字串
while((s = br.readLine()) != null) {
sb.append(s); //把讀取的字串組合起來
}
System.out.println(sb);
br.close();
pw.close();
如上面程式碼所示,列印流可以完成寫入資料操作。而且列印流比常規輸出流功能更加強大,例如PrintWriter可以指定輸出文字使用何種字符集。也可以在構造引數中指定自動重新整理。如果不想覆蓋原來的資料,使用該類的append()方法,就會在檔案尾部新增內容。
(七)使用列印流來完成文字拷貝:
//使用列印流PrintStream來完成檔案拷貝
public static void copyFile(File src, File dest) throws Exception {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(src));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dest));
PrintStream ps = new PrintStream(bos,true);
byte[] buf = new byte[1024];
int len = 0;
while((len = bis.read(buf)) != -1) {
ps.write(buf);
}
ps.close();
bos.close();
bis.close();
}
上面程式碼使用列印流來簡單實現了檔案拷貝操作,利用了列印流建構函式的自動重新整理。使用列印流還有一個好處就是不需要檢查異常。
常用的IO流都已實現,由於篇幅過長,剩餘的知識點會放在下一章節中。