1.1. 文件拷貝操作
需求:把copy_before.txt文件中的數據拷貝到copy_after.txt文件中
private static void copy() throws Exception {
//1):創建源或者目標對象
File src = new File("file/copy_before.txt");
File dest = new File("file/copy_after.txt");
//2):創建IO流對象
FileReader in = new FileReader(src);
FileWriter out = new FileWriter(dest);
//3):具體的IO操作
int len = -1;//記錄以及讀取了多個字符
char[] buff = new char[1024];//每次可以讀取1024個字符
len = in.read(buff);//先讀取一次
while(len > 0) {
//邊讀邊寫
out.write(buff, 0, len);
len = in.read(buff);//再繼續讀取
}
//4):關閉資源(勿忘)
out.close();
in.close();
}
如何,正確處理異常:
private static void copy2() {
//1):創建源或者目標對象
File src = new File("file/copy_before.txt");
File dest = new File("file/copy_after.txt");
//把需要關閉的資源,聲明在try之外
FileReader in = null;
FileWriter out = null;
try {
//可能出現異常的代碼
//2):創建IO流對象
in = new FileReader(src);
out = new FileWriter(dest);
//3):具體的IO操作
int len = -1;//記錄以及讀取了多個字符
char[] buff = new char[1024];//每次可以讀取1024個字符
len = in.read(buff);//先讀取一次
while (len > 0) {
out.write(buff, 0, len);
len = in.read(buff);//再繼續讀取
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//4):關閉資源(勿忘)
try {
if (out != null) {
out.close();
}
} catch (Exception e) {
e.printStackTrace();
}
try {
if (in != null) {
in.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
此時關閉資源的代碼,又臭又長,在後續的學習中爲了方便就直接使用throws拋出IO異常了,在實際開發中需要處理。
1.2. 緩衝流
節點流的功能都比較單一,性能較低。處理流,也稱之爲包裝流,相對於節點流更高級,這裏存在一個設計模式——裝飾設計模式,此時撇開不談。
包裝流如何區分?寫代碼的時候,發現創建流對象時,需要傳遞另一個流對象,類似:
new 流類A( new 流類B(..));
那麼流A就屬於包裝流,當然B可能屬於節點流也可能屬於包裝流。
有了包裝流之後,我們只關係包裝流的操作即可,比如只需要關閉包裝流即可,無需在關閉節點流。
非常重要的包裝流——緩衝流,根據四大基流都有各自的包裝流:
BufferedInputStream / BufferedOutputStream / BufferedReader / BufferedWriter
緩衝流內置了一個默認大小爲8192個字節或字符的緩存區,緩衝區的作用用來減少磁盤的IO操作,拿字節緩衝流舉例,比如一次性讀取8192個字節到內存中,或者存滿8192個字節再輸出到磁盤中。
操作數據量比較大的流,都建議使用上對應的緩存流。
需求:把郭德綱-報菜名.mp3文件中的數據拷貝到郭德綱-報菜名2.mp3文件中
private static void copy3() throws Exception {
//1):創建源或者目標對象
File src = new File("file/郭德綱-報菜名.mp3");
File dest = new File("file/郭德綱-報菜名2.mp3");
//2):創建IO流對象
BufferedInputStream bis =
new BufferedInputStream(new FileInputStream(src), 8192);
BufferedOutputStream bos =
new BufferedOutputStream(new FileOutputStream(dest), 8192);
//3):具體的IO操作
int len = -1;//記錄以及讀取了多個字符
byte[] buff = new byte[1024];//每次可以讀取1024個字符
len = bis.read(buff);//先讀取一次
while (len > 0) {
//邊讀邊寫
bos.write(buff, 0, len);
len = bis.read(buff);//再繼續讀取
}
//4):關閉資源(勿忘)
bos.close();
bis.close();
}
1.3. 對象序列化
序列化:指把Java堆內存中的對象數據,通過某種方式把對象數據存儲到磁盤文件中或者傳遞給給網絡上傳輸。序列化在分佈式系統在應用非常廣泛。
反序列化:把磁盤文件中的對象的數據或者把網絡節點上的對象數據恢復成Java對象的過程。
需要做序列化的類必須實現序列化接口:java.io.Serializable(這是標誌接口[沒有抽象方法])
可以通過IO中的對象流來做序列化和反序列化操作。
-
ObjectOutputStream:通過writeObject方法做序列化操作的
-
ObjectInputStream:通過readObject方法做反序列化操作的
如果字段使用transient 修飾則不會被序列化。
class User implements **Serializable** {
private String name;
private transient String password;
private int age;
public User(String name, String password, int age) {
this.name = name;
this.password = password;
this.age = age;
}
public String getName() {
return name;
}
public String getPassword() {
return password;
}
public int getAge() {
return age;
}
public String toString() {
return "User [name=" + name + ", password=" + password + ", age=" + age + "]";
}
}
測試代碼
public class ObjectStreamDemo {
public static void main(String[] args) throws Exception {
String file = "file/obj.txt";
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
User u = new User("Will", "1111", 17);
out.writeObject(u);
out.close();
//--------------------------------------
ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
Object obj = in.readObject();
in.close();
System.out.println(obj);
}
}
obj.txt文件
image.png
序列化的版本問題
當類實現Serializable接口後,在編譯的時候就會根據字段生成一個缺省的serialVersionUID值,並在序列化操作時,寫到序列化數據文件中。
但隨着項目的升級系統的class文件也會升級(增加一個字段/刪除一個字段),此時再重新編譯,對象的serialVersionUID值又會改變。那麼在反序列化時,JVM會把對象數據數據中的serialVersionUID與本地字節碼中的serialVersionUID進行比較,如果值不相同(意味着類的版本不同),那麼報異常InvalidCastException,即:類版本不對應,不能進行反序列化。如果版本號相同,則可以進行反序列化。
image.png
爲了避免代碼版本升級而造成反序列化因版本不兼容而失敗的問題,在開發中我們可以故意在類中提供一個固定的serialVersionUID值。
class User implements Serializable {
private static final long serialVersionUID = 1L;
//TODO
}
1.4. 查漏補缺
1.4.1. 打印流
打印流是一種特殊是輸出流,可以輸出任意類型的數據,比一般的輸出流更好用。可以作爲處理流包裝一個平臺的節點流使用,平時我們使用的System.out.println其實就是使用的打印流。
-
PrintStream :字節打印流
-
PrintWriter :字符打印流
打印流中的方法:
-
提供了print方法:打印不換行
-
提供了println方法:先打印,再換行
private static void test5() throws Exception {
//1):創建源或者目標對象
File dest = new File("file/result3.txt");
//2):創建IO流對象
PrintStream ps = new PrintStream(new FileOutputStream(dest));
//3):具體的IO操作
ps.println("Will");
ps.println(17);
ps.println("衆裏尋他千百度,驀然回首,那人卻在,燈火闌珊處。");
//4):關閉資源(勿忘),打印流可以不用關閉
}
1.4.2. 標準IO
標準的輸入:通過鍵盤錄入數據給程序
標準的輸出:在屏幕上顯示程序數據
在System類中有兩個常量int和out分別就表示了標準流:
InputStream in = System.in;
PrintStream out = System.out;
需求:做一個ECHO(回聲)的小案例
public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in); //接受用戶輸入數據後敲回車
while (sc.hasNextLine()) { //判斷用戶是否輸入一行數據
String line = sc.nextLine(); //獲取用戶輸入的數據
System.out.println("ECHO:" + line);//顯示用戶輸入的數據
}
}
1.5. IO流小結
在開發中使用比較多的還是字節和字符流的讀寫操作,務必要非常熟練,再體會一下六字箴言(讀進來,寫出去),到底有何深意。
綜合練習題:做一個統計代碼行數的程序,掃描一個目錄能統計出該目錄中包括所有子目錄中所有Java文件的行數,不統計空行。