一、IO概述
1、IO流:即InputOutput的縮寫。
2、特點:
- IO流用來處理設備間的數據傳輸。
- Java對數據的操作是通過流的方式。
- Java用於操作流的對象都在IO包中。
- 流按操作數據分爲兩種:字節流和字符流。
- 流按數據傳輸分爲:輸入流和輸出流。
- 注意:流只能操作數據,而不能操作文件。
3、IO流的常用基類
- 字節流的抽象基流:InputStream和OutputStream
- 字符流的抽象基流:Reader和Writer
如InputStream子類FileInputStream;Reader子類FileReader
二、字符流
1、簡述
1)字符流中的對象融合了默認編碼表,即當前系統使用的編碼。2)字符流只用於處理文字數據,而字節流可以處理媒體數據。
3)IO流是用於操作數據的,數據的常見體現形式是文件。FileWriter用於操作文件對象:該流對象一初始化,就必須有被操作的文件存在。
2、字符流的讀寫
a)、寫入字符流(FileWriter)步驟:
- 創建一個FileWriter對象,並關聯被操作的文件即文件存放的目的。
方式二:FileWriter(String fileNme, boolean append):boolean 值爲true代表不覆蓋已有的文件,在文件末尾處寫入數據。
- 調用write方法,將字符寫入到流中。
- 調用flush()方法,刷新該流的緩衝,將數據刷新到目的地中。
- 調用close()方法,關閉流資源。但是關閉前會刷新一次內部的緩衝數據,並將數據刷新到目的地中。
package com.huang.io.iotest;
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterDemo {
public static void main(String[] args) throws IOException {
/*
* 創建一個filewriter對象。該對象一初始化就必須明確操作的文件。該文件會放到指定目錄下,
* 如果該文件存在,則會覆蓋原文件
*/
FileWriter fw = new FileWriter("D:\\file.txt");
// 調用wirte方法,寫入數據
fw.write("huangxiang");
// 刷新流對象中的數據,講數據刷新到目的地中。
fw.flush();
fw.write("haha");
fw.flush();
/*
* 關閉流資源,但其關閉前會先刷新一次流中的數據,將數據刷新到目的地中。
* 與flush的區別,flush刷新後還能繼續使用,close後將關閉流,無法繼續使用。
*/
fw.close();
// fw.write("關閉之後不能再寫入數據!!!!");
}
}
若我們需要對文件進行復寫,而不是覆蓋時,我們可以使用FileWriter(String fileNme, boolean append),代碼示例如下:
package com.huang.io.iotest;
import java.io.FileWriter;
import java.io.IOException;
/*
* 對已有的文件進行續寫
*/
public class FileWriterDemo3 {
public static void main(String[] args) {
FileWriter fw = null;
try {
// true表示在文件末尾進行續寫。不進行覆蓋。
fw = new FileWriter("D:\\file.txt", true);
fw.write("shkui");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
注意:a、其實java自身不能寫入數據,而是調用系統內部方式完成數據的書寫,使用系統資源後,一定要關閉資源。
b、FileWriter 創建文件,當指定文件不存在,就會先創建文件,如果文件存在,則會清空文件中的內容。
並在已有文件的末尾處進行數據續寫。(windows系統中的文件內換行用\r\n兩個轉義字符表示,在linux系統中只用\n表示換行)
c、在創建對象時,如果指定的位置不存在,則會發生IOException異常,所以在整個步驟中,需要對IO異常進行try處理。
d、其實字符流默認封裝了緩衝區(用的都是數組),且底層是用的是字節流的緩衝區。因爲讀取流每次讀取一個字符是兩個字節,
所以要當數組中存儲兩個字節才能寫入,因爲用到了緩衝區,所以要進行刷新操作。
3、IO異常處理方式
原理:爲讓流對象作用域在整個方法中,要讓引用指向null,且資源必須關閉(關閉也會發生異常)。IO中的異常處理方式的代碼示例:
package com.huang.io.iotest;
import java.io.FileWriter;
import java.io.IOException;
/**
*@author huangxiang
*@version 1.0
*/
/*
* IO異常的處理方式
*/
public class FileWriterDemo2 {
public static void main(String[] args) {
/*
* 這裏要在外面建立引用,如果在try內進行FileWriter fw = new FileWriter();
* 在finally代碼塊中fw將不能使用。
*/
FileWriter fw = null;//在外面建立引用
try {
// fw = new FileWriter("K:\\file.txt");//這裏會報FileNotFoundException錯誤,不存在K盤。
fw = new FileWriter("D:\\file.txt");//在內部建立對象。
fw.write("abcde");
} catch (IOException e) {
System.out.println(e.toString());
} finally {
try {//單獨處理關閉失敗異常
//健壯性判斷。。如果fw爲空,就沒有關閉的可能。
//當fw爲空時,會報空指針異常錯誤。
if (fw != null) {
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
b)、讀取字符流(FileReader)
步驟:
- 創建一個文件讀取流對象和指定文件關聯。要保證該文件已經存在,若不存在,將會發生異常FileNotFoundException。
- 調用讀取流對象的read()方法。
第二種方式:通過字符數組,int read(char[] buf):將字符讀入數組,返回讀取的字符數,如果已達到流的末尾,則返回-1。
重載形式: int read(char[] cbuf, int off, int len): 將字符讀入數組的某一部分
- 讀取後要調用close方法將流資源關閉。
package com.huang.io.iotest;
import java.io.FileReader;
import java.io.IOException;
public class FileReaderDemo {
// private static int ch;
public static void main(String[] args) throws IOException {
//創建一個文件讀取流對象,和指定的文件相關聯
//要保證該文件是存在的,如果不存在,會發生異常
FileReader fr = new FileReader("D:\\file.txt");
System.out.println("*****************第一種讀取方式******************");
//read(),一次讀取一個
/*int ch = fr.read();
System.out.println("ch="+(char)ch);*/
//全部讀取
/*
int ch = 0;
while((ch = fr.read()) != -1){
System.out.println("ch="+(char)ch);
}
*/
System.out.println("******************第二種讀取方式*********************");
//通過字符數組進行讀取
//定義一個字符數組,用於存儲讀取到的字符,該read(char[])返回的是讀到的字符個數
char[] buf = new char[1024];
int num = 0;
while ((num = fr.read(buf))!= -1) {
System.out.println(new String(buf,0,num));
}
fr.close();
}
}
3、字符流緩衝區
①概述:其實就是帶有緩衝區的字符流(內部已經封裝了數組),緩衝區的出現是爲了提高流的操作效率而出現的,所以在創建緩衝區之前,必須要有一個流對象。②對應類
- BufferedReader
- BufferedWriter
④特有方法
- readLine():一次讀取一行文本,返回該行內容的字符串,如果已到達流末尾,則返回 null 。
- newLine():寫入一個換行符,具有跨平臺性。
package com.huang.io.buffertest;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class BufferedReaderDemo {
public static void main(String[] args) throws IOException {
// 創建一個讀取流對象和文件相關聯
FileReader fr = new FileReader("D:\\file.txt");
// 爲了提高效率,創建一個緩衝區,將字符讀取流對象作爲參數傳遞給緩衝區對象的構造方法。
BufferedReader br = new BufferedReader(fr);
//讀一行的方法readLine,方便對文件的讀取,當讀取完畢後,返回空。
// String ch = br.readLine();
// System.out.println(ch);
String ch = null;
//字符串讀取不爲null時。。
while ((ch = br.readLine()) != null) {
// br.flush();
System.out.println(ch);
}
br.close();
}
}
BufferedWriter使用的代碼演示:
package com.huang.io.buffertest;
/*
* 緩衝區的出現是爲了提高流的操作效率而出現的。
* 所以在創建緩衝區之前得先有流對象。
*
* 該緩衝區提供了一個跨平臺的換行符:newLine();
*/
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class BufferedWriterDemo {
public static void main(String[] args) throws IOException {
//創建一個字符寫入流對象
FileWriter fw = new FileWriter("D:\\file.txt",true);
//爲了提高字符寫入效率,加入了緩衝技術
//只要將需要被提高效率的流對象作爲參數傳遞給緩衝區的構造函數即可。
BufferedWriter bw = new BufferedWriter(fw);
//跨平臺的換行符
bw.newLine();
bw.write("huangxiang");
//*只要用到緩衝區就必須刷新!!!
bw.flush();
fw.close();
//其實關閉緩衝區,就是在關閉緩衝區中的流對象,可不寫。
// bw.close();
}
}
緩衝區小練習,通過緩衝區複製一個.java文件,並結合前面所學的IO異常處理方式進行異常處理。
package com.huang.io.demo;
/*
* 通過標準io異常處理方式處理異常。
*/
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
/*
* 通過緩衝區複製一個.java文件
*/
public class CopyTextDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
BufferedReader bufr = null;
BufferedWriter bufw = null;
try {
bufr = new BufferedReader(new FileReader("D:\\ball.java"));
bufw = new BufferedWriter(new FileWriter("E:\\copy.txt"));
String line = null;
while ((line = bufr.readLine()) != null) {
bufw.write(line);
bufw.newLine();
bufw.flush();
}
} catch (IOException e) {
throw new RuntimeException("讀寫失敗");
} finally {
try {
if (bufr != null)
bufr.close();
} catch (IOException e) {
throw new RuntimeException("讀取關閉失敗");
}
try {
if (bufw != null)
bufw.close();
} catch (IOException e) {
throw new RuntimeException("寫入關閉失敗");
}
}
}
}
四、裝飾設計模式
1、什麼是裝飾設計模式?
當想要對已有的對象進行功能增強時,那麼可以自定義類,將已有對象傳入,基於已有的功能,並提供加強功能,那麼自定義的該類稱爲裝飾類。裝飾類通常會通過構造方法接收被裝飾的類,並基於被裝飾的對象的功能,提供更強的功能。2、裝飾和繼承的區別:
- 裝飾模式比繼承要靈活。避免了繼承體系的臃腫,而且降低了類與類之間的關係。
- 裝飾類因爲增強已有對象,具備的功能和已有的是相同的,只不過提供了更強的功能,所以裝飾類和被裝飾的類通常都是屬於一個體系。
- 從繼承結構轉爲組合結構。
- 在設計模式那篇博客中有詳細介紹。
package com.huang.design.demo;
/*
* 裝飾設計模式:
* 所謂裝飾設計模式,就是對已有對象的功能進行增強。
*/
class Person{
void eat(){
System.out.println("吃飯");
}
}
/*
* 爲了不隨便改動原來的代碼,還要增強其功能。
* 我們可以定義一個新類,對原有對象進行裝飾
*/
class NewPerson{
private Person p;
NewPerson(Person p){
this.p = p;
}
public void eat(){
System.out.println("飯前來杯酒");
p.eat();
System.out.println("飯後來根菸");
}
}
public class DecorationDesignDemo {
/**
* @author huangxiang
* @param args
*/
public static void main(String[] args) {
Person p1 = new Person();
System.out.println("月底吃飯的方式(裝飾前)");
p1.eat();
NewPerson p2 = new NewPerson(p1);
System.out.println("月初吃飯的方式(裝時候)");
p2.eat();
}
}
五、字節流
1、字節流讀寫文件
①概述- 字節流和字符流的基本操作是相同的,但字節流還可以操作其他媒體文件。
- 媒體文件(所有文件)數據中都是以字節(而進制形式)存儲的,所以字節流對象可直接對媒體文件的數據寫入到文件中,不用再進行刷流動作。
- 讀取字節流:InputStream 寫輸出流OutputStream
因爲字節流操作的是字節,即數據的最小單位,不需要像字符流一樣要進行轉換爲字節。所以可直接將字節數據寫入到指定文件中。
② InputStream 常用方法
- 字節流對象的read()方法。
- InputStream特有方法: int available();//返回文件中的字節個數
當文件過大時,此數組長度所佔內存空間就會溢出。所以,此方法慎用,當文件不大時,可以使用。
③OutputStream 常用方法
- 向輸出流中寫入一個字節: void write(int b)//會將read讀取的int型數據強轉爲byte型,寫入最低8位,保持數據的真確信。
- 寫入字節數組: void write(byte[] b),void write(byte[] b,int off,int len)
package com.huang.io.demo;
import java.io.FileInputStream;
import java.io.IOException;
/*
* FileInputStream是InputStream的子類,是操作文件的字節輸出流,專門用於提取文件中的數據。
* 通過循環語句來實現數據的持續讀取
*/
public class FileInputStreamDemo {
public static void main(String[] args) throws IOException {
//創建一個文件字節輸入流
FileInputStream fis = new FileInputStream("D:\\file.txt");
//定義一個int類型的變量b,記住每次讀取的一個字符
int b = 0;
while(true){
b = fis.read();
if(b==-1)
break;
System.out.println((char)b);
}
fis.close();
}
}
字節流練習:複製一個MP3文件package com.huang.put.demo;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.OutputStream;
/*
* 需求:複製一個MP3
* MP3是字節流文件,需一個字節一個字節複製。
* 思路:
* 1.創建一個字節輸出流,並關聯源文件
* 2.創建一個字節輸入流,並關聯其目的地
* 3.循環讀取
* 4.將讀取到的數據寫入目的地文件
*/
public class CopyFileDemo {
public static void main(String[] args) throws IOException {
// 創建一個輸出流,並關聯需要複製的文件
InputStream is = new FileInputStream("F:\\Music\\Beyond - 情人.mp3");
// 創建一個輸入流,並關聯目的地
OutputStream os = new FileOutputStream("D:\\情人.mp3");
/*
* 循環讀取,寫入
*/
// 獲取拷貝前的系統時間
long begintime = System.currentTimeMillis();
// 定義一個變量,記住每次讀取的字節
int len = 0;
while ((len = is.read()) != -1) {
os.write(len);
}
// 獲取拷貝後的系統時間
long endtime = System.currentTimeMillis();
long spelltime = endtime - begintime;
System.out.println("拷貝前的時間" + begintime + "拷貝結束的時間" + endtime + ",總共花了"
+ spelltime + "時間");
is.close();
os.close();
}
}
2、字節流的緩衝區
①字節流緩衝區的出現同樣是爲了提高讀寫效率。②字節流緩衝區:定義一個字節數組作爲和緩衝區,讀寫文件時,一次性讀取多個字節數據,並保存到字節數組中,然後將字節數組中的數據一次性寫入文件。
③讀寫特點:
- read():會將字節byte型值提升爲int型值
- write():會將int型強轉爲byte型,即保留二進制數的最後八位。
因爲有可能會讀到連續8個二進制1的情況,8個二進制1對應的十進制是-1.那麼就會數據還沒有讀完,就結束的情況。因爲我們判斷讀取結束是通過結尾標記-1來確定的。
所以,爲了避免這種情況將讀到的字節進行int類型的提升。並在保留原字節數據的情況前面了補了24個0,變成了int類型的數值。而在寫入數據時,只寫該int類型數據的最低8位。
④原理:在該對象中封裝指定長度字節數組,定義指針和計數器記錄判斷讀取數組的長度及數組中元素是否取完。如:read()方法是一次性將指定長度的字節數據存入,然後一次返回緩衝區中的一個字節數據,只要緩衝區中還有數據,就繼續返回,當緩衝區中數據爲零時,繼續讀取指定長度的字節數據存入字節數組中。⑤字段摘要:
- protected byte[] buf 存儲數據的內部緩衝區數組。
- protected int count 此值始終處於 0 到 buf.length 的範圍內。
- protected int pos 緩衝區中的當前位置,是將從 buf 數組中讀取的下一個字符的索引。。
package com.huang.put.demo;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/*
* 需求:複製一個MP3
* 思路:前面通過一個字節一個字節循環讀取的方式,可以複製成功
* 但所需要花費的時間過長,效率太低,先需要提高效率,加入字節流的緩衝區。
* 步驟:
* 1.創建字節輸入流,並於源文件相關聯
* 2.創建字節輸入流,並關聯目的地
* 3.使用緩衝區讀寫文件
* 4.循環讀寫
*/
public class FastCopyFileDemo {
public static void main(String[] args) throws IOException {
// 創建一個輸出流,並關聯需要複製的文件
InputStream is = new FileInputStream("F:\\Music\\Beyond - 情人.mp3");
// 創建一個輸入流,並關聯目的地
OutputStream os = new FileOutputStream("D:\\情人.mp3");
//加入字節流緩衝區
byte[] buff = new byte[1024];
/*
* 循環讀取,寫入
*/
// 獲取拷貝前的系統時間
long begintime = System.currentTimeMillis();
// 定義一個變量,記住每次讀取的字節
int len = 0;
while ((len = is.read(buff)) != -1) {
os.write(buff, 0, len);;
}
// 獲取拷貝後的系統時間
long endtime = System.currentTimeMillis();
long spelltime = endtime - begintime;
System.out.println("拷貝前的時間" + begintime + "拷貝結束的時間" + endtime + ",總共花了"
+ spelltime + "時間");
is.close();
os.close();
/*
* 加入緩衝區前運行結果:
* 拷貝前的時間1432147828169拷貝結束的時間1432147902739,總共花了74570時間
* 加入緩衝區後運行結果:
* 拷貝前的時間1432147756740拷貝結束的時間1432147756878,總共花了138時間
* 效率大大提高。
*/
}
}
六、標準輸入輸出流
1、概述
System.in:in爲 InputStream 類型,是標準輸入流,用於讀取鍵盤輸入的數據。System.out:out爲 PrintStream 類型,對應的是標準的輸出設備,控制檯。
2、弊端
使用輸入流進行鍵盤錄入時,只能一個字節一個字節進行錄入。爲了提高效率,希望能像字符流錄入整行數據也就是readLine方法。這就需要用到轉換流,將字節流轉換成字符流。代碼演示如下:
package com.huang.io.demo;
import java.io.IOException;
import java.io.InputStream;
/*
* 讀取鍵盤錄入
* System.out:對應的是標準輸出設備,控制檯
* System.in:對應的是標準輸入設備,鍵盤
*
* 需求:當錄入一行數據之後,就將該數據進行打印,
* 如果錄入的數據是over,那麼停止打印
*/
public class ReadIn {
public static void main(String[] args) throws IOException {
InputStream in = System.in;
StringBuilder sb = new StringBuilder();
while(true){
//判斷空格\r\n
int ch = in.read();
if (ch=='\r')
continue;
if(ch=='\n'){
String s = sb.toString();
if("over".equals(s))
break;
System.out.println(s.toUpperCase());
sb.delete(0, sb.length());
}
else {
sb.append((char)ch);
}
}
}
}
七、轉換流
1、概述
①轉換流也是一種包裝流,可以實現字符和字節相互之間的轉換,可以在其構造函數中指定編碼表,默認使用的是系統的編碼。②爲了提高讀寫效率,可以通過 BufferedReader 和 BufferedWriter 對轉換流進行包裝。
③爲什麼要出現轉換流呢?
當字節流中的數據都是字符時,轉成字符流效率更高。
2、InputStreamReader
原因:該對象用的是InputStream的read方法,將數據以字節流轉化爲內存中的字符流,終點是內存。3、OutputStreamWriter
原因:通過源碼不難發現其用的是OutputStream的write方法,從內存中讀取剛纔讀進來的字符流,將內存中的數據以字節流的形式輸出。其實輸入與輸出是相反地一個過程。因爲文件最終是以二進制形式存儲的。
看源碼就知道,FileWriter的write會調用OutputStreamWriter的write方法,而outputStreamWriter的write方法會調用outputStream的write方法。
代碼演示如下所示:
package com.huang.io.demo;
/*
* 讀取轉化流
*
* 字節轉字符
*/
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class TransStreamDemo {
public static void main(String[] args) throws IOException {
// //獲取鍵盤錄入對象
// InputStream in = System.in;
// //將字節流對象轉換爲字符流對象InputStreamReader
// InputStreamReader isr = new InputStreamReader(in);
//爲了提高效率,將字符串進行緩衝區技術高效操作。 BufferedReader
// BufferedReader bufr = new BufferedReader(isr);
//******鍵盤錄入的最常見寫法:將上面三句話合爲一句
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while((line = bufr.readLine())!= null){
if("over".equals(line))
break;
System.out.println(line.toUpperCase());
}
bufr.close();
}
}
八、小知識點
1、改變標準輸入輸出流( System 類的方法)static void setIn(InputStream in) :重新分配“標準”輸入流。
static void setOut(PrintStream out) : 重新分配“標準”輸出流。
2、異常的日誌信息
通過異常體系的 void printStackTrace(PrintStream s) 將異常信息寫入文件中。
3、系統信息
通過 Properties 類的 void list(PrintStream out) 將屬性列表輸出到指定的輸出流。
(!!!!)4、流操作的基本規律(在代碼中以註釋的形式分析)
package com.huang.io.demo;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
/*
* 流操作的基本規律:
* 1.明確源和目的地
* 源:輸入流 InputStream ,Reader
* 目的地:輸出流OutPutStream,Writer
* 2.操作數據是否爲純文本 是:字符流 否:字節流
* 3.當體系明確後,要明確使用哪個的具體對象
* 通過設備來進行區分 源設備:內存,硬盤。鍵盤 目的設備:內存,硬盤。控制檯。
*/
/*
* 需求:將一個文本中的數據複製到另外一個文本中。 1.源:InputStream ,Reader
*/
public class TransStreamDemo2 {
public static void main(String[] args) throws IOException {
BufferedReader bufr = new BufferedReader(new InputStreamReader(
System.in));
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(
System.out));
String line = null;
while ((line = bufr.readLine()) != null) {
if ("over".equals(line))
break;
System.out.println(line.toUpperCase());
}
bufr.close();
bufw.close();
}
}