1. 程式人生 > >JavaSE(六)IO程式設計

JavaSE(六)IO程式設計

IO程式設計

IO 基礎

簡介

IO:Input/Output
IO流是一種流式資料輸入、輸出模型:

1)二進位制資料以byte為最小單位在InputStream/OutputStream中單向流動
2)字元資料以char為最小單位在Reader/Writer中單向流動
3)JDK的java.io包提供了同步IO功能
4)JDK的java.nio包提供了非同步IO功能

Java的IO流的介面

位元組流介面:InputStream、OutputStream
字元流介面:Reader、Writer

File物件

java.io.File 表示檔案系統的一個檔案或者目錄

isFile():判斷是否是檔案
isDirectory():判斷是否是目錄

建立File物件本身不涉及IO操作

getPath():獲取路徑
getAbsolutePath():獲取絕對路徑
getCanonicalPath():獲取規範路徑

檔案操作

canRead():是否允許讀取該檔案
canWrite():是否允許寫入該檔案
canExecute():是否允許執行該檔案
length():獲取檔案大小
createNewFile():建立一個新檔案
static createTempFile():建立一個臨時檔案
delete():刪除該檔案
deleteOnExit():在JVM退出時刪除檔案

目錄操作

String[] list():列出目錄下的檔案和子目錄名
File[] listFiles():列出目錄下的檔案和子目錄名
File[] listFiles(FileFilter filter)
File[] listFiles(FilenameFilter filter)
mkdir():建立該目錄
mkdirs():建立該目錄,並在必要時將不存在的父目錄也創建出來
delete():刪除該目錄

練習

編寫程式,1)列出當前目錄下的所有子目錄和檔案,並按層次列印;2)如果不指定引數,則使用當前目錄,如果指定引數,則使用指定目錄

public static void
printFileName(File file){ if(file.isDirectory() && 0 != file.listFiles().length){ File[] files = file.listFiles(); files = sortFile(files); for(File f : files){ StringBuilder sb = new StringBuilder(); if(f.isFile()){ sb.append(getTab(number)); sb.append(f.getName()); }else{ sb.append(getTab(number)); sb.append(f.getName()); sb.append("\\"); } System.out.println(sb); if(f.isDirectory()){ number++; printFileName(f); number--; } } } } //得到子目錄前需要加多少tab private static String getTab(int number){ StringBuilder sb = new StringBuilder(); for(int i = 0; i < number; i++){ sb.append("\t"); } return sb.toString(); } //對子目錄進行排序,資料夾在前,檔案在後 private static File[] sortFile(File[] files) { ArrayList<File> list = new ArrayList<File>(); for(File f : files){ if(f.isDirectory()){ list.add(f); } } for(File f : files){ if(f.isFile()){ list.add(f); } } return list.toArray(new File[files.length]); }

Input 和 Output

InputStream

InputStream是所有輸入流的超類

1)int read():讀取一個位元組,並返回位元組(0~255),已讀到末尾返回-1
2)int read(byte[]):讀取若干位元組並填充到byte[]陣列
3)int read(byte[] b, int off, int len)指定byte[]陣列的偏移量和最大填充數
4)void close():關閉輸入流
read()方法是阻塞的

使用try可以保證InputStream正確關閉

public static void readFile(){
	InputStream input = null;
	try {
		input = new FileInputStream("src/1.txt");
		int n;
		while((n = input.read()) != -1){
			System.out.println(n);
		}
	} catch(IOException e){
		e.printStackTrace();
	}finally {
		if(input != null){
			try {
				input.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
// JDK7 新特性
public static void readFile() throws FileNotFoundException, IOException{
	try(InputStream input = new FileInputStream("src/1.txt")){
		int n;
		while((n = input.read()) != -1){
			System.out.println(n);
		}
	} // 在此自動關閉InputStream(推薦寫法)
}
// 利用快取區一次讀取多個位元組
public static void readFile() throws FileNotFoundException, IOException{
	try(InputStream input = new FileInputStream("src/1.txt")){
		byte[] buffer = new byte[1024];
		int n;
		while((n = input.read(buffer)) != -1){
			System.out.println("read " + n + " bytes.");
		}
	}
}

常用的InputStream:
1)FileInputStream:可以從檔案獲取輸入流
2)ByteArrayInputStream:可以在記憶體中模擬一個InputStream

byte[] data = {72, 101, 108, 111, -28, -67, -96, -27, -91, -67};
try(InputStream input = new ByteArrayInputStream(data)){// 可以用在測試中
	int n;
	while((n = input.read()) != -1){
		System.out.println(n)
	}
}

OutputStream

outputStream是所有輸出流的超類

1)void write(int b):寫入一個位元組
2)void write(byte[]):寫入byte[]陣列的所有位元組
3)void write(vyte[] b, int off, int len):寫入byte[]陣列指定範圍的位元組
4)void close():關閉輸入流
5)void flush():將快取區內容輸出
write()方法也是阻塞的

使用try可以保證OutputStream正確關閉
常用OutputStream
1)FileOutputStream:可以輸出到檔案
2)ByteArrayOutputStream:可以在記憶體中模擬一個OutputStream

try(ByteArrayOutputStream output = new ByteArrayOutputStream()){
	output.write("Hello ".getBytes("UTF-8"));
	output.write("world!".getBytes("UTF-8"));
	byte[] data = output.toByteArray();
}

練習

編寫程式,接收兩個命令列引數,分別表示原始檔和目標檔案,用InputStream、OutputStream把原始檔複製到目標檔案。複製後,請檢查原始檔和目標檔案是否相同(檔案長度相同,內容相同),分別用文字檔案、圖片檔案和zip檔案測試

public class Main {
	public static void main(String[] args) throws FileNotFoundException, IOException {
		if (args.length < 2) {
			System.out.println("usage: java CopyFile sourcefile targetfile");
			System.exit(0);
		}
		byte[] bytes = new byte[1024];
		try(InputStream input = new FileInputStream(args[0]);
			OutputStream output = new FileOutputStream(args[1])){
			int n;
			while((n = input.read(bytes)) != -1){
				output.write(bytes, 0, n);
			}
		}
	}
}

Filter模式

Filter模式是為了解決子類數量爆炸的問題
直接提供資料的InputStream:

1)FileInputStream
2)ByteArrayInputStream
3)ServletInputStream

提供附加功能的InputStream從FilterInputStream派生

1)BufferedInputStream:快取功能
2)DigestInputStream:計算簽名功能
3)CipherInputStream:加密/解密功能
4)GZIPInputStream:壓縮功能

在這裡插入圖片描述

InputStream input = new GZIPInputStram(
	new BufferedInputStream(
		new FileInputStream("test.gz");
	)
)

Filter模式又稱Decorator模式,通過少量的類是實現了各種功能的組合
FilterInputStream 和 FilterOutputStream類似
在這裡插入圖片描述

// 實現一個位元組數計數的InputStream
public class CounInputStream extends FilterInputStream{
	int count = 0;
	public CounInputStream(InputStream in) {
		super(in);
	}
	@Override
	public int read(byte[] b, int off, int len) throws IOException {
		int n = super.read(b, off, len);
		count++;
		return n;
	}
}

操作Zip

ZipInputStream可以讀取Zip流
JarInputStream提供了額外讀取jar包內容的功能
ZipOutputStream可以寫入Zip流
配合FileInputStream和FileOutputStream就可以讀寫Zip檔案

// ZipInputStream的基本使用
try(ZipInputStream zip = new ZipInputStream("test.zip")){
	ZipEntry entry = null;
	while((entry = zip.getNextEntry()) != null){
		String name = entry.getName();
		if(!entry.isDirectory()){
			int n;
			while((n = zip.read()) != -1){
				// ...
			}
		}
	}
}
// ZipOutputStream的基本使用
try(ZipOutputStream zip = new ZipOutputStream("test.zip")){
	File[] files = ...;
	for(File file : files){
		zip.putNextEntry(new ZipEntry(file.getName));
		zip.write(getFileDataAsBytes(file));
		zip.closeEntry();
	}
}

classpath資源

classpath中也可以包含任意型別的檔案
從classpath讀取檔案可以避免不同環境下檔案路徑不一致的問題
讀取classpath資源:

try(InputStream input = getClass().getResourceAsStream("/default.properties")){
	if(input != null){
		// Read from classpath
	}
}

序列化

序列化是指把一個Java物件變成二進位制內容(byte[]),序列化後可以把byte[]儲存到檔案中,也可以通過網路傳輸
Java物件實現序列化必須實現Serializable介面

Serializable介面沒有定義任何方法,空介面被稱為“標記介面(Marker Interface)”

反序列化是指把一個二進位制內容(bytep[])變成Java物件
使用ObjectOutputStream和ObjectInputStream實現序列化和反序列化

try(ObjectOutputStream output = new ObjectOutputStream(...)){
	output.writeObject(new Person("aa"));
	output.writeObject(new Person("bb"));
}
try(ObjectInputStream input = new ObjectInputStream(...)){
	Object obj = input.readObject();
	Person per = (Person)input.readObject();
}

readObject()可能丟擲異常:

1)ClassNotFoundException:沒有找到對應的Class
2)InvalidClassException:Class不匹配

反序列化的重要特點:反序列化由JVM直接構造出Java物件,不呼叫構造方法
可設定serialVersionUID作為版本號(非必須)

Reader 和 Writer

Reader

Reader以字元為最小單位實現了字元輸入

1)int read():讀取下一個字元,並返回字元(0~65535),讀到末尾返回-1
2)int read(char[]):讀取若干字元並填充到char[]陣列
3)int read(char[] c, int off, int len):指定char[]陣列的偏移量和最大填充數
4)void close():關閉Reader

常用的Reader類

1)FileReader:從檔案讀取
2)CharArrayReader:從char[]陣列讀取,可以再記憶體中模擬一個Reader

Reader是基於InputStream構造的,任何InputStream都可指定編碼並通過InputStreamReader轉換為Reader:

InputStream input = new FileInputStream(filename)
Reader reader = new InputStreamReader(input, "UTF-8")

Writer

Writer以位元組為最小單位實現了字元流輸出

1)write(int c):寫入下一個字元
2)write(cahr[]):寫入char[]陣列的所有字元

常用Write類:

1)FileWriter:寫入檔案
2)CharArrayWriter:寫入cahr[]陣列

Writer是基於OutputStream構造的,任何Output都可指定編碼並通過OutputStreamWriter轉換為Writer:

Writer writer = new OutputStreamWriter(output, "UTF-8")

練習

編寫一個程式,接收兩個命令列引數,分別表示原始檔和目標檔案,然後用Reader、Writer把GBK編碼的原始檔轉換為UTF-8編碼的目標檔案

public class Main {
	public static void main(String[] args) throws FileNotFoundException,
			IOException {
		if (args.length < 2) {
			System.out.println("usage: java CopyFile sourcefile targetfile");
			System.exit(0);
		}
		char[] chars = new char[1024];
		try (Reader reader = new InputStreamReader(
				new FileInputStream(args[0]), "GBK");
				Writer writer = new OutputStreamWriter(new FileOutputStream(
						args[1]), "UTF-8")) {
			int ch;
			while ((ch = reader.read(chars)) != -1) {
				writer.write(chars, 0, ch);
			}
		}
	}
}