JavaIO編程

IO基礎
  IO簡介
    IO
    IO流
    同步和異步
      API
    總結
  File對象
    方法
    文件
    目錄
    總結
Input和Output
  InputStream
    方法
    特點
    總結
  OutputStream
    方法
    特點
    總結
  Filter模式
    介紹
    總結
  操作Zip
    ZipInputStream
    ZipOutputStream
    總結
  classpath資源
    讀取文件
    總結
  序列化與反序列化
    序列化
    序列化條件
    反序列化
      基本使用
    常見異常
    注意
    總結
Reader和Writer
  Reader
    介紹
    特點
    Reader和InputStream
    總結
  Writer
    介紹
    特點
    Writer和OutputStream
    總結

IO基礎

IO簡介

IO

IO是指Input / Output,即輸入和輸出

  • Input指從外部讀入數據到內存,例如,讀文件,從網絡讀取等
  • Output指把數據從內存輸出到外部,例如,寫文件,輸出到網絡等
IO流

IO流是一種順序讀寫數據的模式:

  • 單向流動
  • 以byte爲最小單位(字節流)

如果字符不是單字節表示的ASCII:

  • Java提供了Reader / Writer表示字符流
  • 字符流傳輸的最小數據單位是char
  • 字符流輸出的byte取決於編碼方式

Reader / Writer本質上是一個能自動編解碼的InputStream / OutputStream

同步和異步

同步IO:

  • 讀寫IO時代碼等待數據返回後才繼續執行後續代碼
  • 代碼編寫簡單,CPU執行效率低

異步IO:

  • 讀寫IO時僅發出請求,然後立刻執行後續代碼
  • 代碼編寫複雜,CPU執行效率高
API
  • JDK提供的java.io是同步IO(基礎)
  • JDK提供的java.nio是異步IO(高級)

在這裏插入圖片描述

總結
  • IO流是一種流式的數據輸入/輸出模型
  • 二進制數據以byte爲最小單位在InputStream / OutputStream中單向流動
  • 字符數據以char爲最小單位在Reader / Writer中單向流動
  • JDK的java.io包提供了同步IO功能
  • Java的IO流的接口和實現類是分離的:
    • 字節流接口:InputStream / OutputStream
    • 字符流接口:Reader / Writer

File對象

方法

java.io.File表示文件系統的一個文件或者目錄:

  • 構造方法:File(String pathname)
File f = new File("/usr/bin/javac"); // Linux
File f = new File("C:\\Users\test.txt"); // Windows
  • String getPath() / getAbsolutePath() / getCanonicalPath()
File f = new File("..");
String path = f.getPath(); // ".." 傳入的路徑
String aPath = f.getAbsolutePath(); // "/usr/.." 絕對路徑
String cPath = f.getCanonicalPath(); // "/usr"  規範路徑
  • boolean isFile():是否是文件
  • boolean isDirectory():是否是目錄
new File("C:\\Windows\\notepad.exe").isFile(); // true
new File("C:\\Windows").isDirectory();	// true
文件

當File對象表示一個文件時(isFile() == true):

  • boolean canRead():是否允許讀取該文件
  • boolean canWrite():是否允許寫入該文件
  • boolean canExecute():是否允許運行該文件
  • long length():獲取文件大小
  • boolean createNewFIle():創建一個新文件
  • static boolean createTempFile(String prefix, String suffix):創建一個臨時文件
  • boolean delete():刪除該文件
  • void deleteOnExit():在JVM退出時刪除該文件
目錄

當File對象表示一個目錄時(isDirectory() == true):

  • String[] list():列出目錄下的文件和子目錄名
  • File[] listFiles():列出目錄下的文件和子目錄名
  • File[] listFiles(FileFilter filter):
  • FIle[] listFiles(FilenameFilter filter):
  • boolean mkdir():創建該目錄
  • boolean mkdirs():創建該目錄,並在必要時將不存在的父目錄也創建出來
  • boolean delete():刪除該目錄
File dir = new File("C:\\Sample\\test");
dir.mkdir(); // C:\Sample必須存在
dir.mkdirs();	// 如果C:\Sample不存在就自動創建
總結

File對象表示一個文件或者目錄:

  • 創建File對象本身不涉及IO操作
  • 獲取路徑 / 絕對路徑 / 規範路徑:
    • getPath() / getAbsolutePath() / getCanonicalPath()
  • 可以獲取目錄的文件和子目錄
  • 通過File對象可以創建或刪除文件和目錄

Input和Output

InputStream

方法

java.io.InputStream是所有輸入流的超類:

  • abstract int read()
    • 讀取下一個字節,並返回字節(0~255)
    • 如果已讀到末尾,返回-1
  • int read(byte[] b):讀取若干字節並填充到byte[]數組,返回讀取的字節數
  • int read(byte[] b, int off, int len):指定byte[]數組的偏移量和最大填充數
  • void close():關閉輸入流
public void readFile() throws IOException {
  	InputStream input = null;
    try {
        input = new FileInputStream("src/readme.txt");
        int n;
        
		} finally {
  			if (input != null) {
        		input.close();  	
        }
    }
}

// try(resource)格式
public void readFile() throws IOException {
    try (InputStream input = new FileInputStream("src/readme.txt")) {
        int n;
        while ((n = input.read()) != -1) {
            System.out.println(n);
        }
    }// 自動關閉InputStream
}

// 利用緩衝區一次讀取多個字節:
public void readFile() throws IOException {
    try (InputStream input = new FileInputStream("src/readme.txt")) {
        byte[] buffer = new byte[1000];
      	int n;
      	while ((n = input.read(buffer)) != -1) {
        		System.out.println("read " + n + " bytes"); 	
        }
    }
}
特點
  • read()方法是阻塞(blocking)的
int n ;
// 必須等待read()方法返回才能執行下一行代碼
n = input.read();
int m = n;
  • FileInputStream可以從文件獲取輸入流
try (InputStream input = new FileInputStream("test.txt")) {}
  • ByteArrayInputStream可以在內存中模擬一個InputStream:
byte[] data = {72, 111, 12, 43, 103, -11, -34, -43};
try (InputStream input = new ByteArrayInputStream(data)) {
  	int n;
  	while ((n = input.read() != -1)) {
      	System.out.println(n);
    }
}
總結
  • InputStream定義了所有輸入流的超類
  • FileInputStream實現了文件流輸入
  • ByteArrayInputStream在內存中模擬一個字節流輸入
  • 使用try(resource)保證InputStream正確關閉

OutputStream

方法

java.io.OutputStream是所有輸出流的超類:

  • abstract write(int b):寫入一個字節
  • void write(byte[] b):寫入byte[]數組的所有字節
  • void write(byte[] b, int off, int len):寫入byte[]數組指定範圍的字節
  • void close():關閉輸出流
  • void flush():將緩衝區的內容輸出
// 將byte寫入OutputStream
public void writeFile() throws IOException {
  	OutputStream output = new FileOutputStream("out/readme.txt");
  	output.write(72);		// H
  	output.write(101);	// e
  	output.write(108);	// l
  	output.write(108);	// l
  	output.write(111);	// o
  	output.close();
}

// try(resource)自動關閉資源
public void writeFile() throws IOException {
  	try (OutputStream output = new FileOutputStream("out/readme.txt")) {
      	output.write(72);		// H
        output.write(101);	// e
        output.write(108);	// l
        output.write(108);	// l
        output.write(111);	// o
    }// 自動關閉資源
}
// 將byte[]寫入OutputStream
public void writeFile() throws IOException {
  	try (OutputStream output = new FileOutputStream("out/readme.txt")) {
      	byte[] b = "Hello".getBytes("UTF-8");
        output.write(b);
    }
}
特點
  • write()方法是阻塞(blocking)的
int n = 65;
// 必須等待write()方法返回才能執行下一行代碼
output.write(n);
int m = n;
  • FileOutputStream可以輸出到文件:
try (OutputStream output = new FileOutputStream("test.out")){}
  • ByteArrayOutputStream可以在內存中模擬一個OutputStream:
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
  	output.write("Hello".getBytes("UTF-8"));
  	output.write("world!".getBytes("UTF-8"));
  	byte[] data = output.toByteArray();
}
總結
  • OutputStream定義了所有輸出流的超類
  • FileOutputStream實現了文件流輸出
  • ByteArrayOutputStream在內存中模擬一個字節流輸出
  • 使用try(resource)保證OutputStream正確關閉

Filter模式

介紹

JDK把InputStream分爲兩類:

  • 直接提供數據的InputStream:FileInputStream、ByteArrayOutputStream、ServletInputStream…
  • 提供額外附加功能的InputStream:BufferedInputStream、DigestInputStream、CipherInputStream…

在這裏插入圖片描述

使用InputStream時,根據情況進行組合以實現不同的功能:

InputStream input = new GZIPInputStream(new BufferedInputStream(new FileInputStream("test.gz")));
  • 組合功能而非繼承的設計模式稱爲Filter模式(或Decorator模式)
  • 通過少量的類實現了各種功能的組合
總結
  • Java IO使用Filter模式爲InputStream / OutputStream增加功能
  • 可以把一個InputStream和任意FilterInputStream組合
  • 可以把一個OutputStream和任意FilterInputStream組合
  • Filter模式可以在運行期動態地增加功能(又稱爲Decorator模式)

操作Zip

ZipInputStream
try (ZipInputStream zip = new ZipInputStream(new BufferedInputstream(new FileInputStream("test.zip")))) {
  	// 文件或目錄
  	ZipEntry entry = null;
  	while ((entry = zip.getNextEntry()) != null) {
      	String name = entry.getName();
      	if (!entry.isDirectory()) {
          	int n ;
          	while ((n = zip.read()) != -1) {
              	System.out.println(n);
            }
        }
    }
}
ZipOutputStream

可以直接寫入Zip的內容

try (ZipOutputStream zip = new ZipOutputStream(new BufferedInputstream(new FileInputStream("test.zip")))) {
  	File[] files = ...
    for (File file : files) {
      	zip.putNextEntry(new ZipEntry(file.getName()));
      	zip.write(getFileDataAsBytes(file));
      	zip.closeEntry();
    }
}
總結
  • ZipInputStream可以讀取Zip格式的流
  • ZipOutputStream可以把數據寫入Zip
  • ZipInputStream / ZipOutputStream都是FilterInputStream / FilterOutputStream
  • 配合FileInputStream和FileOutputStream就可以讀寫zip文件

classpath資源

讀取文件
  • 把資源放在classpath下可以避免不同環境下文件路徑不一致的問題,例如,從Windows換到Mac時絕對路徑需要修改,放在classpath下使用相對路徑就不用修改了
try (InputStream input = getClass().getResourceAsStream("/default.properties")) {
  	// 如果文件不存在會返回null,所以需要進行判斷
    if (input != null) {
      	...
    }
}
總結
  • 把資源存儲在classpath中可以避免文件路徑依賴
  • Class對象的getResourceAsStream()可以從classpath讀取資源
  • 需要檢查返回的InputStream是否爲null

序列化與反序列化

序列化

序列化是指把一個Java對象編程二進制內容(byte[])

  • 序列化後可以把byte[]保存到文件中
  • 序列化後可以把byte[]通過網絡傳輸
序列化條件

一個Java對象要能序列化,必須實現Serializable接口:

  • Serializable接口沒有定義任何方法
  • 空接口被稱爲”標記接口“(Marker Interface)
反序列化

反序列化是指把一個二進制內容(byte[])變成Java對象

  • 反序列化後可以從文件讀取byte[]並變爲Java對象
  • 反序列化後可以從網絡讀取byte[]並變爲Java對象
基本使用

ObjectOutputStream負責把一個Java對象寫入二進制流:

try (ObjectOutputStream output = new ObjectOutputStream(...)) {
  	output.writeObject(new Person("Xiao Ming"));
  	output.writeObject(new Person("Xiao Hong"));
}

ObjectInputStream負責從二進制流讀取一個Java對象:

try (ObjectInputStream input = new ObjectInputStream(...)) {
  	Object o1 = input.readObject();
  	Person p = (Person)input.readObject();
}
常見異常

readObejct()可能拋出的異常:

  • ClassNotFoundException:沒有找到對象的Class
  • InvalidClassException:Class不匹配
注意

反序列化的重要特點:

  • 反序列化由JVM直接構造出Java對象,不調用構造方法
  • 被序列化和反序列化的對象建議添加一個serialVersionUID常量
public class Person implements Serializable {
  	// 序列化版本ID
		private static final long serialVersionUID = 3439329373292337L;
  	...
}
總結
  • 可序列化的Java對象必須實現java.io.Serializable接口
  • 類似Serializable這樣的空接口被稱爲“標記接口”(Marker Interface)
  • 反序列化時不調用構造方法
  • 可設置serialVersionUID作爲版本號(非必須)
  • Java的序列化機制僅適用於Java,如果需要與其他語言交換數據,必須使用通用的序列化方法,例如JSON

Reader和Writer

Reader

java.io.Reader和java.io.InputStream的區別
在這裏插入圖片描述

介紹

java.io.Reader是所有字符輸入流的超類

  • int read()
    • 讀取下一個字符,並返回字符(0~65535)
    • 如果已讀到末尾,返回-1
  • int read(char[] c):讀取若干字符並填充到char[]數組,返回讀取的字符數
  • int read(char[] c, int off, int len):指定char[]數組的偏移量和最大填充數
  • void close():關閉Reader
// 完整地讀取一個Reader所有字符:
public void readFile() throws IOException {
  	Reader reader = null;
  	try {
        reader = new FileReader("readme.txt");
        int n;
        while ((n = reader.read()) != -1) {
            System.out.println((char)n);
        }
    } finally {
      	if (reader != null) {
      			reader.close();
        }
    }
}
// try(resource)寫法
public void readFile() throws IOException {
  	try (Reader reader = new FileReader("readme.txt")) {
        int n;
        while ((n = reader.read()) != -1) {
            System.out.println((char)n);
        }
    } // 自動關閉Reader
}
// 利用緩衝區一次讀取多個字符
public void readFile() throws IOException {
  	try (Reader reader = new FileReader("readme.txt")) {
        char[] buffer = new char[1000];
        int n;
      	while((n = reader.read(buffer) != -1) {
        		System.out.println("read " + n + " chars");
        }
    }
}

特點
  • read()方法是阻塞(blocking)的
int n ;
// 必須等待read()方法返回才能執行下一行代碼
n = input.read();
int m = n;
  • FileReader可以從文件獲取Reader
try (Reader reader = new FileReader("test.txt")){
  	// 使用的是系統默認編碼
}
  • CharArrayReader可以在內存中模擬一個Reader
char[] data = {'H', 'e', 'l', 'l', 'o'};
try (Reader reader = new CharArrayReader(data)) {
  	int n ;
  	while ((n = reader.read() != -1)) {
      	System.out.println((char)n);
    }
}
Reader和InputStream

Reader實際上是基於InputStream構造的:

  • FileReader內部持有一個FileInputStream
  • Reader可以通過InputStream構造
InputStream input = new FileInputStream("test.txt");
Reader reader = new InputStreamReader(input, "UTF-8");
...
reader.close();// 不需要調用input.close()
總結
  • Reader定義了所有字符輸入流的超類
  • FileReader實現了文件字符流輸入
  • CharArrayReader在內存中模擬一個字符流輸入
  • Reader是基於InputStream構造的:
    • FileReader使用系統默認編碼
    • 可以通過InputStreamReader指定編碼
  • 使用try(resource)保證Reader正確關閉

Writer

java.io.Writer和java.io.OutputStream的區別
在這裏插入圖片描述

介紹

java.io.Writer是所有字符輸出流的超類

  • void write(int c):寫入一個字符(0~65535)
  • void write(char[] c):寫入字符數組的所有字符
  • void write(char[] c, int off, int len):寫入字符數組指定範圍的字符
  • void write(String s):寫入String表示的所有字符
// 向Writer寫入字符
public void writeFile() throws IOException {
		Writer writer = null;
  	try {
      	writer = new FileWriter("readme.txt");
      	writer.writer(54);
    } finally {
      	writer.close();
    }
}
// try(resource)寫法
public void writeFile() throws IOException {
		try (Writer writer = new FileWriter("readme.txt")) {
      	writer.writer(54);
    }// 自動關閉writer
}
// 一次寫入多個字符
public void writeFile() throws IOException {
		try (Writer writer = new FileWriter("readme.txt")) {
      	writer.writer("Hello".toCharArray());
      	writer.write("world.");
    }
}
特點
  • write()方法是阻塞(blocking)的
int n = 65;
// 必須等待write()方法返回才能執行下一行代碼
output.write(n);
int m = n;
  • FileWriter可以輸出到文件:
try (Writer writer = new FileWriter("test.txt")){// 編碼是系統默認編碼}
  • CharArrayWriter可以在內存中模擬一個Writer:
try (Writer writer = new CharArrayWriter()) {
  	writer.write(65);
  	writer.write(66);
  	writer.write(67);
  	char[] data = writer.toCharArray();//{'A', 'B', 'C'}
 		
}
Writer和OutputStream

Writer實際上是基於OutputStream構造的:

  • FileWriter內部持有一個FileOutputStream
  • Writer可以通過OutputStream構造
OutputStream output = new FileOutputStream("readme.txt");
Writer writer = new OutputStreamWriter(output, "UTF-8");
...
writer.close();// 不要調用output.close(),內部會自動調用
總結
  • Writer定義了所有字符輸出流的超類
  • FileWriter實現了文件字符流輸出
  • CharArrayWriter在內存中模擬一個字符流輸出
  • Writer是基於OutputStream構造的:
    • FileWriter使用系統默認編碼
    • 可以通過OutputStreamWriter指定編碼
  • 使用try(resource)保證Writer正確關閉

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