JAVA進階(05)IO技術
一、入門
1、資料來源
(1)資料來源分為:
- 源裝置:為程式提供資料,一般對應 輸入流
- 目標裝置:程式資料的目的地,一般對應 輸出流
2、資料流
3、典型的IO流程式碼
public static void main(String[] args) { FileInputStream fis = null; try { fis = new FileInputStream("d:/a.txt"); // 內容是:abc StringBuilder sb = new StringBuilder(); int temp = 0; //當temp等於-1時,表示已經到了檔案結尾,停止讀取 while ((temp = fis.read()) != -1) { sb.append((char) temp); } System.out.println(sb); } catch (Exception e) { e.printStackTrace(); } finally { try { //這種寫法,保證了即使遇到異常情況,也會關閉流物件。 if (fis != null) { fis.close(); } } catch (IOException e) { e.printStackTrace(); } } }
4、流概念細分
(1)按流的方向分類:
- 輸入流:資料來源 到 程式(以InputStream、Reader結尾的流)。
- 輸出流:程式 到 目的地(以OutPutStream、Writer結尾的流)。
(2)按處理的資料單元分類:
- 位元組流:以位元組為單位讀寫資料,如FileInputStream、FileOutputStream。
- 字元流:以字元為單位讀寫資料,命名上以Reader/Writer結尾的流一般是字元流,如FileReader、FileWriter。
(3)按處理物件不同分類:
- 節點流:可以直接從資料來源或目的地讀寫資料,如FileInputStream、FileReader、DataInputStream等。
- 處理流:不直接連線到資料來源或目的地,通過對其他流的處理提高程式的效能,如BufferedInputStream、BufferedReader等。
5、IO流類體系
(1)四大IO抽象類
1、InputStream:
- int read():讀取一個位元組的資料,並將位元組的值作為int型別返回(0-255之間的一個值),返回值為-1表示讀取結束。
- void close():
2、OutputStream:
- void write(int n):向目的地中寫入一個位元組。
- void close():關閉輸出流物件,釋放相關係統資源。
3、Reader:
- int read(): 讀取一個字元的資料,並將字元的值作為int型別返回(Unicode值),返回值為-1表示讀取結束。
- void close() : 關閉流物件,釋放相關係統資源。
4、Writer:
- void write(int n): 向輸出流中寫入一個字元。
- void close() :
(2)各種流總結
- FileInputStream/FileOutputStream
節點流:以位元組為單位直接操作“檔案”。
- ByteArrayInputStream/ByteArrayOutputStream
節點流:以位元組為單位直接操作“位元組陣列物件”。
- ObjectInputStream/ObjectOutputStream
處理流:以位元組為單位直接操作“物件”。
- DataInputStream/DataOutputStream
處理流:以位元組為單位直接操作“基本資料型別與字串型別”。
- FileReader/FileWriter
節點流:以字元為單位直接操作“文字檔案”(注意:只能讀寫文字檔案)。
- BufferedReader/BufferedWriter
處理流:將Reader/Writer物件進行包裝,增加快取功能,提高讀寫效率。
- BufferedInputStream/BufferedOutputStream
處理流:將InputStream/OutputStream物件進行包裝,增加快取功能,提高 讀寫效率。
- InputStreamReader/OutputStreamWriter
處理流:將位元組流物件轉化成字元流物件。
- PrintStream
處理流:將OutputStream進行包裝,可以方便地輸出字元,更加靈活。
二、IO流應用
1、檔案位元組流(FileInputStream/ FileOutputStream)
(1)檔案複製的實現
static void copyFile(String src, String dec) {
FileInputStream fis = null;
FileOutputStream fos = null;
//為了提高效率,設定快取陣列!(讀取的位元組資料會暫存放到該位元組陣列中)
byte[] buffer = new byte[1024];
int temp = 0;
try {
fis = new FileInputStream(src);
fos = new FileOutputStream(dec);
//邊讀邊寫
//temp指的是本次讀取的真實長度,temp等於-1時表示讀取結束
while ((temp = fis.read(buffer)) != -1) {
/*將快取陣列中的資料寫入檔案中,注意:寫入的是讀取的真實長度;
*如果使用fos.write(buffer)方法,那麼寫入的長度將會是1024,即快取
*陣列的長度*/
fos.write(buffer, 0, temp);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//兩個流需要分別關閉
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
(2)注意
- 通常設定快取陣列來提高效率,讀取時:read(byte[] b);寫入時:write(byte[ ] b, int off, int length)
- 每個流都要單獨關閉
2、檔案字元流(FileReader/FileWriter)
(1)檔案複製的實現
public static void main(String[] args) {
// 寫法和使用Stream基本一樣。只不過,讀取時是讀取的字元。
FileReader fr = null;
FileWriter fw = null;
int len = 0;
try {
fr = new FileReader("d:/a.txt");
fw = new FileWriter("d:/d.txt");
//為了提高效率,建立緩衝用的字元陣列
char[] buffer = new char[1024];
//邊讀邊寫
while ((len = fr.read(buffer)) != -1) {
fw.write(buffer, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fw != null) {
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fr != null) {
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3、 緩衝位元組流( BufferedInputStream/BufferedOutputStream)
(1)原理
- 緩衝流是先將資料快取起來,然後當快取區存滿後或者手動重新整理時再一次性的讀取到程式或寫入目的地
(2)檔案複製
/**緩衝位元組流實現的檔案複製的方法*/
static void copyFile1(String src, String dec) {
FileInputStream fis = null;
BufferedInputStream bis = null;
FileOutputStream fos = null;
BufferedOutputStream bos = null;
int temp = 0;
try {
fis = new FileInputStream(src);
fos = new FileOutputStream(dec);
//使用緩衝位元組流包裝檔案位元組流,增加緩衝功能,提高效率
//快取區的大小(快取陣列的長度)預設是8192,也可以自己指定大小
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
while ((temp = bis.read()) != -1) {
bos.write(temp);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//注意:增加處理流後,注意流的關閉順序!“後開的先關閉!”
try {
if (bos != null) {
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (bis != null) {
bis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
(3) 注意
- 在關閉流時,後開的先關閉。
- 快取區的大小預設是8192位元組,也可以使用其它的構造方法自己指定大小。
4、緩衝字元流(BufferedReader/BufferedWriter)
public static void main(String[] args) {
// 注:處理文字檔案時,實際開發中可以用如下寫法,簡單高效!!
FileReader fr = null;
FileWriter fw = null;
BufferedReader br = null;
BufferedWriter bw = null;
String tempString = "";
try {
fr = new FileReader("d:/a.txt");
fw = new FileWriter("d:/d.txt");
//使用緩衝字元流進行包裝
br = new BufferedReader(fr);
bw = new BufferedWriter(fw);
//BufferedReader提供了更方便的readLine()方法,直接按行讀取文字
//br.readLine()方法的返回值是一個字串物件,即文字中的一行內容
while ((tempString = br.readLine()) != null) {
//將讀取的一行字串寫入檔案中
bw.write(tempString);
//下次寫入之前先換行,否則會在上一行後邊繼續追加,而不是另起一行
bw.newLine();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bw != null) {
bw.close();
}
} catch (IOException e1) {
e1.printStackTrace();
}
try {
if (br != null) {
br.close();
}
} catch (IOException e1) {
e1.printStackTrace();
}
try {
if (fw != null) {
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fr != null) {
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
(1) 注意
- readLine()方法是BufferedReader特有的方法
- 寫入一行後要記得使用newLine()方法換行
5、位元組陣列流(ByteArrayInputStream|ByteArrayOutputStream)
public static void test(byte[] b) {
ByteArrayInputStream bais = null;
StringBuilder sb = new StringBuilder();
int temp = 0;
//用於儲存讀取的位元組數
int num = 0;
try {
//該構造方法的引數是一個位元組陣列,這個位元組陣列就是資料來源
bais = new ByteArrayInputStream(b);
while ((temp = bais.read()) != -1) {
sb.append((char) temp);
num++;
}
System.out.println(sb);
System.out.println("讀取的位元組數:" + num);
} finally {
try {
if (bais != null) {
bais.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
(1)注意
- ByteArrayInputStream是把 位元組陣列 當做資料來源
- 用在需要 流 和 陣列 之間轉化的情況
6、資料流(DataInputStream和DataOutputStream)
(1)實現對基本資料型別和字串型別的讀寫
public static void main(String[] args) {
DataOutputStream dos = null;
DataInputStream dis = null;
FileOutputStream fos = null;
FileInputStream fis = null;
try {
fos = new FileOutputStream("D:/data.txt");
fis = new FileInputStream("D:/data.txt");
//使用資料流對緩衝流進行包裝,新增緩衝功能
dos = new DataOutputStream(new BufferedOutputStream(fos));
dis = new DataInputStream(new BufferedInputStream(fis));
//將如下資料寫入到檔案中
dos.writeChar('a');
dos.writeInt(10);
dos.writeDouble(Math.random());
dos.writeBoolean(true);
dos.writeUTF("北京尚學堂");
//手動重新整理緩衝區:將流中資料寫入到檔案中
dos.flush();
//直接讀取資料:讀取的順序要與寫入的順序一致,否則不能正確讀取資料。
System.out.println("char: " + dis.readChar());
System.out.println("int: " + dis.readInt());
System.out.println("double: " + dis.readDouble());
System.out.println("boolean: " + dis.readBoolean());
System.out.println("String: " + dis.readUTF());
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
7、 物件流(ObjectInputStream/ObjectOutputStream)
/**使用物件輸出流將資料寫入檔案*/
public static void write(){
// 建立Object輸出流,幷包裝緩衝流,增加緩衝功能
OutputStream os = null;
BufferedOutputStream bos = null;
ObjectOutputStream oos = null;
try {
os = new FileOutputStream(new File("d:/bjsxt.txt"));
bos = new BufferedOutputStream(os);
oos = new ObjectOutputStream(bos);
// 使用Object輸出流
oos.writeInt(12);
oos.writeDouble(3.14);
oos.writeChar('A');
oos.writeBoolean(true);
oos.writeUTF("北京");
oos.writeObject(new Date());
} catch (IOException e) {
e.printStackTrace();
} finally {
//關閉輸出流
if(oos != null){
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(bos != null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(os != null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**使用物件輸入流將資料讀入程式*/
public static void read() {
// 建立Object輸入流
InputStream is = null;
BufferedInputStream bis = null;
ObjectInputStream ois = null;
try {
is = new FileInputStream(new File("d:/bjsxt.txt"));
bis = new BufferedInputStream(is);
ois = new ObjectInputStream(bis);
// 使用Object輸入流按照寫入順序讀取
System.out.println(ois.readInt());
System.out.println(ois.readDouble());
System.out.println(ois.readChar());
System.out.println(ois.readBoolean());
System.out.println(ois.readUTF());
System.out.println(ois.readObject().toString());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 關閉Object輸入流
}
}
(1)注意
- 物件流不僅可以讀寫物件,還可以讀寫基本資料型別。
- 使用物件流讀寫物件時,該物件必須序列化與反序列化。
- 系統提供的類(如Date等)已經實現了序列化介面,自定義類必須手動實現序列化介面。
- 示例程式碼中在 read 之前必須先關閉輸出流,不然會報 EOF 異常。
8、轉換流(InputStreamReader/OutputStreamWriter)
(1)用來實現將位元組流轉化成字元流
public static void main(String[] args) {
// 建立字元輸入和輸出流:使用轉換流將位元組流轉換成字元流
BufferedReader br = null;
BufferedWriter bw = null;
try {
br = new BufferedReader(new InputStreamReader(System.in));
bw = new BufferedWriter(new OutputStreamWriter(System.out));
// 使用字元輸入和輸出流
String str = br.readLine();
// 一直讀取,直到使用者輸入了exit為止
while (!"exit".equals(str)) {
// 寫到控制檯
bw.write(str);
bw.newLine();// 寫一行後換行
bw.flush();// 手動重新整理
// 再讀一行
str = br.readLine();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 關閉字元輸入和輸出流
}
}
三、序列化
1、概述
(1)序列化和反序列化
- Java 提供了一種物件序列化的機制,該機制中,一個物件可以被表示為一個位元組序列,該位元組序列包括該物件的資料、有關物件的型別的資訊和儲存在物件中資料的型別。
- 把Java物件轉換為位元組序列的過程稱為物件的序列化。把位元組序列恢復為Java物件的過程稱為物件的反序列化。
(2)序列化的前提
- java類必須實現 java.io.Serializable介面
(3) 序列化的作用
- 持久化: 把物件的位元組序列永久地儲存到硬碟上,通常存放在一個檔案中
- 網路通訊:在網路上傳送物件的位元組序列
2、序列化物件
(1)ObjectOutputStream 類用來序列化一個物件,方法void writeObject(Object x),示例如下:
import java.io.*;
public class SerializeDemo
{
public static void main(String [] args)
{
Employee e = new Employee();
e.name = "Reyan Ali";
e.address = "Phokka Kuan, Ambehta Peer";
e.SSN = 11122333;
e.number = 101;
try
{
FileOutputStream fileOut =
new FileOutputStream("/tmp/employee.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(e);
out.close();
fileOut.close();
System.out.printf("Serialized data is saved in /tmp/employee.ser");
}catch(IOException i)
{
i.printStackTrace();
}
}
}
(2)注意:
- 序列化到副檔名一般為.ser
- static屬性不參與序列化
- 不想被序列化的屬性,不能使用static,而是使用transient修飾(private transient String attr5;)
- 為了防止讀和寫的序列化ID不一致,一般指定一個固定的序列化ID
3、反序列化物件
(1)示例:
import java.io.*;
public class DeserializeDemo
{
public static void main(String [] args)
{
Employee e = null;
try
{
FileInputStream fileIn = new FileInputStream("/tmp/employee.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
e = (Employee) in.readObject();
in.close();
fileIn.close();
}catch(IOException i)
{
i.printStackTrace();
return;
}catch(ClassNotFoundException c)
{
System.out.println("Employee class not found");
c.printStackTrace();
return;
}
System.out.println("Deserialized Employee...");
System.out.println("Name: " + e.name);
System.out.println("Address: " + e.address);
System.out.println("SSN: " + e.SSN);
System.out.println("Number: " + e.number);
}
}
(2)注意事項:
- JVM反序列化物件的前提是必須是能夠找到該類的class檔案物件,找不到的話,readObject() 方法會丟擲一個 ClassNotFoundException 異常
- 有屬性被標記為transient,反序列化後,該屬性為預設值
- 反序列化後得到的是Object物件,一般需要強轉為相應的型別
四、工具類( Apache IOUtils和FileUtils)
1、裝飾器模式
(1)概念:實現對原有類的包裝和裝飾,使新的類具有更強的功能
(2)程式碼演示:
class Iphone {
private String name;
public Iphone(String name) {
this.name = name;
}
public void show() {
System.out.println("我是" + name + ",可以在螢幕上顯示");
}
}
class TouyingPhone {
public Iphone phone;
public TouyingPhone(Iphone p) {
this.phone = p;
}
// 功能更強的方法
public void show() {
phone.show();
System.out.println("還可以投影,在牆壁上顯示");
}
}
public class TestDecoration {
public static void main(String[] args) {
Iphone phone = new Iphone("iphone30");
phone.show();
System.out.println("===============裝飾後");
TouyingPhone typhone = new TouyingPhone(phone);
typhone.show();
}
}
(3)IO流中的裝飾器模式:處理流使用了裝飾器模式,進行了功能的增強
2、FileUtils的使用
public static void main(String[] args) throws Exception {
FileUtils.copyDirectory(new File("d:/aaa"), new File("d:/bbb"), new FileFilter() {
@Override
public boolean accept(File pathname) {
// 使用FileFilter過濾目錄和以html結尾的檔案
if (pathname.isDirectory() || pathname.getName().endsWith("html")) {
return true;
} else {
return false;
}
}
});
}
3、IOUtils的使用